3

I would like to be able to use the value of a variable (or better yet, the return of a function(arg)) as the about string for a CLI program defined using structopt. The end goal is a fully localized CLI that detects the system language or an ENV var and loads up localized strings that get baked into the --help message, etc.

By default it uses the documentation comment:

/// My about string
#[derive(StructOpt)]
struct Cli {}

I've found I can pass a manually entered string instead:

#[derive(StructOpt)]
#[structopt(about = "My about string")]
struct Cli {}

That's one step closer, but what I really want to do is pass a variable:

let about: &str = "My about string";
#[derive(StructOpt)]
#[structopt(about = var!(about))]
struct Cli {}

That last block is pseudo-code because I don't know what syntax to use to achieve this. In the end, I'll need more than just a single string slice, but I figured that was a place to start.

How can I pass through values like this to structopt? Do I need to access the underlying clap interfaces somehow?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Caleb
  • 5,084
  • 1
  • 46
  • 65

1 Answers1

1

StructOpt just adds a derive macro and a corresponding trait over clap. The clap crate has a function to set the about message at runtime, so we just need to add that. If we look at how from_args works we can see that it creates the clap App struct before turning it into the user-defined struct.

Thus, to do what you want:

use structopt::StructOpt;

fn get_localized_about() -> &'static str {
    "localized string"
}

#[derive(Debug, StructOpt)]
struct Foo {}

fn main() {
    let foo = Foo::from_clap(&Foo::clap().about(get_localized_about()).get_matches());
    println!("{:#?}", foo);
}
Timidger
  • 1,199
  • 11
  • 15
  • 1
    Thanks, this definitely answers the exact question that I asked. Unfortunately it seems I may have over simplified the question. This works great for setting the `.about()` info, but I can't get the same effect for other things like my options, subcommands, and their arguments. I want to localize _all_ the output, not just the About string. If I try roughly the same method with `.arg()` I can only see how to add arguments, not change the description of ones added via StructOpt. Am I barking up the wrong tree? StructOpt seemed lovely but maybe I should just be using Clap directly. – Caleb Apr 27 '20 at 20:20
  • It sounds like you might want to investigate the `yaml` feature as part of the crate. You can specify everything in a separate file and then load in various ones depending on the localization as reported by the system. This is documented in the README [here](https://github.com/clap-rs/clap/blob/master/README.md) – Timidger Apr 27 '20 at 20:25
  • @Tinmidger I actually read about Clap's YAML support already, but there are two issues with using it. First, all the docs suggest that it is a compile-time choice where you build binaries with the language you want. I want run-time language selection, not compile time. Second, my translations are coming from Fluent and I'm not thrilled with putting them in YAML. I could auto-generate the YAML though if the first issue wasn't a blocker. – Caleb Apr 27 '20 at 20:32
  • 1) `load_yaml!` is compile time (since it's a macro) but if you [look at it's source](https://docs.rs/clap/2.33.0/src/clap/macros.rs.html#30) it's easy enough to make it a runtime option. If you don't care about binary size you can `include_str!` all of the localized yaml files (or futz around at runtime with the files if you don't want that) and then select which one to use. However, the fact that the translations are coming from fluent does make things harder. In that case I'd drop StructOpt and just use clap, or use a single yaml file and template it based on the localization – Timidger Apr 27 '20 at 20:38
  • I'm about 3 days into Rust here, futzing around with macro sources to move compile time code into runtime sounds like it might have to wait a week or so. But thanks for the tips. I'd definitely like to inline the strings in the binary because I have path issues if I try to load resources. There are three run scenarios and I have to do some path discovery to figure out which it is before I can even find the YAML files, and if that process goes wrong I'd like to be able to display localized error messages! Classic catch 22. – Caleb Apr 27 '20 at 20:44
  • The other catch 22 is that the locale is supposed to come from `LC_LANG` (or platform equivalent). That part already works ... but it's supposed to be overwridable via a CLI option flag `--language tr_TR` or such. Even with Clap directly I can't make out how to parse the args for a language flag **before** setting the arg help properties. – Caleb Apr 27 '20 at 20:44
  • The point about the macro is it's just a convenience -- if you need it to work at runtime you can take the source of it and remove all use of macros until you get to the point you want to move it to "runtime". Also look into "build scripts" to see if maybe that will help you. If you have any other specific questions I suggest asking another stackoverflow question since this has gone beyond the original question :) – Timidger Apr 27 '20 at 21:00
  • Thanks for the suggestions. As far as I can tell build scripts can only pass things to the main code via ENV vars ... or generate source code that gets loaded of course. For this use case there is not much benefit over the main program doing the load as far as I can see. – Caleb Apr 27 '20 at 21:10