Include long options without short options for help and version - rust

I would like to include --help and --version long options without the -h and -V short options. Is this possible?
I'm using clap with yaml. The closest I've been able to come up with is to use hidden (unused) args that mask the short options.
# main.rs
use clap::{load_yaml, App};
fn main() {
let y = load_yaml!("cli.yaml");
let m = App::from_yaml(y).get_matches();
println!("_help {}", m.is_present("_help"));
println!("_version {}", m.is_present("_version"));
}
# Cargo.toml
[package]
name = "app"
version = "0.0.1"
edition = "2018"
[dependencies]
clap = {version = "~2.33.0", features = ["yaml"]}
# cli.yaml
name: app
version: 0.0.1
args:
- opt: { short: "o", long: "opt" }
- _help: { short: "h", hidden: true }
- _version: { short: "V", hidden: true }
$ cargo -q run -- --help
app 0.0.1
USAGE:
app [FLAGS]
FLAGS:
--help Prints help information
-o, --opt
--version Prints version information
$ cargo -q run -- -h
_help true
_version false
$ cargo -q run -- -o
_help false
_version false
$ cargo -q run -- -V
_help false
_version true
$ cargo -q run -- -x
error: Found argument '-x' which wasn't expected, or isn't valid in this context
USAGE:
app [FLAGS]
For more information try --help
This doesn't feel like a very clean approach. Is there another/better way?

Clap is designed more towards creating an args parser. So there aren't really that much functionality for getting and removing existing arguments.
If you simply want to rename "-h" to e.g. "-?", then you can do that with help_short("-?") (see also version_short().)
However, there are ways to work around it.
Assuming you're using e.g. clap = "2.33". Then similarly to what you're already doing, you can override/replace the help and version args, and in that way "remove" the short versions. (For brevity, I'll only include examples for help.)
You can of course keep it in cli.yaml if you want, but I'll add it to main.rs. In short, you want to add a new "help" arg and only give it a long version. It's important that you include help("Prints help information") as this is replacing the existing help arg, so if you don't it won't have the default help message for --help.
The downside to overring "help" is that you'd need to handle print_help() yourself.
use clap::{load_yaml, App, Arg};
fn main() {
let y = load_yaml!("cli.yaml");
let mut app = App::from_yaml(y)
.arg(
Arg::with_name("help")
.long("help")
.help("Prints help information"),
);
let m = app.clone().get_matches();
if m.is_present("help") {
app.print_help().unwrap();
// std::process::exit(0);
// or just
return;
}
}
However, if you're using clap = "3.0.0-beta.2" then that simplifies things, with the introduction of mut_arg(). Because that allows us to mutate the argument. Thereby, we no longer need to call print_help() ourselves.
use clap::{load_yaml, App, Arg};
fn main() {
let y = load_yaml!("cli.yaml");
let m = App::from(y)
.mut_arg("help", |h| {
Arg::new("help")
.long("help")
.about("Prints help information")
})
.get_matches();
}
Note that App::from_yaml() is now App::from(), while Arg::with_name() has become Arg::new(), and help() is now about().

Related

How to catch the output of a compiler error in nim?

I am not sure if this is currently possible (and maybe it is not even advisable), but I would like to be able to catch the output of a compiler error and reuse it in code. An example would be:
type IntOrString = int | string
var s: seq[IntOrString]
this code would not compile with error:
/usercode/in.nim(2, 5) Error: invalid type: 'IntOrString' in this context: 'seq[IntOrString]' for var
I am interested in a way to be able to use the message of this error in the code.
My use case is being able to easily document and discuss compiler errors in nimib. If I were to write a document that shows and discuss the different type of compiler errors catching the message automatically that could be useful (a workaround right now would be to write the code to file and compile with verbosity 0).
It is possible to use the compiler api to catch errors:
import ../compiler/[nimeval, llstream, ast, lineinfos, options], os, strformat
let std = findNimStdLibCompileTime()
let modules = [std, std / "pure", std / "std", std / "core"]
var intr = createInterpreter("script", modules)
intr.registerErrorHook proc(config:ConfigRef,info:TLineInfo,msg:string,severity:Severity) =
raise newException(CatchableError,&"{severity}: {(info.line,info.col)} {msg}")
)
try:
intr.evalScript(llStreamOpen("""type IntOrString = int | string
var s: seq[IntOrString]"""))
except CatchableError as e:
echo e.msg
echo "done"
destroyInterpreter(intr)
outputs:
Error: (2, 4) invalid type: 'IntOrString' in this context: 'seq[IntOrS
tring]' for var
done
Caveat: you can't run runtime code at compile time, for example, trying to run
type Dog = object of RootObj
method speak(i:Dog):string = "woof"
echo Dog().speak()
in the interpreter will give you errors, instead you would have to do something like:
type Dog = object of RootObj
method speak*(i:Dog):string = "woof"
proc newDog*():Dog = discard
let
dog = intr.callRoutine(intr.selectRoutine("newDog"),[])
speech = intr.callRoutine(intr.selectRoutine("speak"),[dog])
if speech.kind == nkStrLit:
echo speech.strval

Mapping errors from Command::new("/bin/bash") arguments

I am executing a bash command, for which I would like to catch an error when the move argument fails. Since there is one more command after move, my goal is to capture specifically whether the move operation got successfully executed to a status code and to break out of the entire Rust program.
Is there a way to accomplish this? I have provided below the source code.
let path : String = "/home/directory/".to_string();
let command = Command::new("bin/bash")
.arg("-c")
.arg("mv somefile1.txt /home/")
.arg("cp ~/somefile2.txt .")
.stdout(Stdio::piped())
.output();
bash -c doesn't accept two commands like that. You could try splitting it into two separate Commands:
Command::new("bash")
.arg("-c")
.arg("mv somefile1.txt /home/")
.status()?
.success() // bool
.then(|| ()) // convert bool to Option
.ok_or("mv failed")?; // convert Option to Result
Command::new("bash")
.arg("-c")
.arg("cp ~/somefile2.txt .")
.status()?
.success() // bool
.then(|| ()) // convert bool to Option
.ok_or("cp failed")?; // convert Option to Result
Or joining them into a single command with &&:
Command::new("bash")
.arg("-c")
.arg("mv somefile1.txt /home/ && cp ~/somefile2.txt .")
.status()?
.success() # bool
.then(|| ()) # convert bool to Option
.ok_or("failed")?; # convert Option to Result
Better yet, use native Rust functions:
mv → std::fs::rename
cp → std::fs::copy
~ → home::home_dir
This avoids the overhead of calling out to bash and is portable to non-Unix operating systems.
use std::fs;
use dirs::home_dir;
if let Some(home) = home_dir() {
fs::rename("somefile1.txt", home.join("somefile1.txt"))?;
fs::copy(home.join("somefile2.txt"), ".")?;
}

Patch a transient git dependency to a specific rev

I depend on crates a and b, where I patched b to a git dependency on ref foo:
# Cargo.toml of my crate
[dependencies]
a = "1.0.0"
b = "1.0.0"
[patch.crates-io]
b = { git = "https://github.com/user/example", rev = "foo" }
a also depends on b as a git dependency, but on no specific ref:
# Cargo.toml of a
[dependencies]
b = { git = "https://github.com/user/example" }
I want to force a to use the same ref for b as I do, which I thought I could do like this:
# The ideal Cargo.toml of my crate
[dependencies]
a = "1.0.0"
b = "1.0.0"
# patch local dependency
[patch.crates-io]
b = { git = "https://github.com/user/example", rev = "foo" }
# patch transient dependency
[patch.'https://github.com/user/example']
b = { git = "https://github.com/user/example", rev = "foo" }
This does not work however, since the source I'm patching still points to the same source, just in a different rev:
error: failed to resolve patches for `https://github.com/user/example`
Caused by:
patch for `b` in `https://github.com/user/example` points to the same source, but patches must point to different sources
[Finished running. Exit status: 101]
My workaround so far is to fork b and patch like this:
# Cargo.toml of my crate using a work around
[dependencies]
a = "1.0.0"
b = "1.0.0"
[patch.crates-io]
b = { git = "https://github.com/me/example", rev = "foo" } # Using my fork
[patch.'https://github.com/user/example']
b = { git = "https://github.com/me/example", rev = "foo" } # Using my fork
This works, but the fork is essentially useless. Is there a better way to do this?
I tried this hack, but it does not work either since it just ignores the rev. The whole GitHub issue makes it look like what I'm trying is not supported at the moment, but it's hard to tell, since it's not exactly the same feature.
Per this GitHub issue, this feature is currently not supported, since git dependencies are differentiated exclusively via URL, not revision. Some URLs are treated as the same, e.g. the .git ending always being stripped, but cargo is still just comparing URLs and nothing else.
We can however use this "feature" to our advantage: Adding a ?ref=foo to the end of the URL will make it look like a new source:
# Fixed Cargo.toml
[dependencies]
a = "1.0.0"
b = "1.0.0"
# patch local dependency
[patch.crates-io]
b = { git = "https://github.com/user/example?rev=foo" }
# patch transient dependency
[patch.'https://github.com/user/example']
b = { git = "https://github.com/user/example?rev=foo" }
Be careful to use the URL hack in both patches, otherwise you will create a corrupt Cargo.lock with two definitions of b:
# Corrupt Cargo.toml
[dependencies]
a = "1.0.0"
b = "1.0.0"
# patch local dependency
[patch.crates-io]
b = { git = "https://github.com/user/example", rev = "foo" } # mistake!
# patch transient dependency
[patch.'https://github.com/user/example']
b = { git = "https://github.com/user/example?rev=foo" }
While I did not test it, this approach should in principle also work with patches on branches by appending /tree/<branch> to the URL.

How can I get clap to wrap long help messages?

Here is an example:
extern crate clap;
use clap::{Arg, App};
fn main() {
let args = App::new("test")
.arg(Arg::with_name("scoring_method")
.short("s")
.long("scoring-method")
.help("Very very very very very very long long long long long help help help help help message message message message message message message"))
.get_matches();
}
Leads to the help text formatting like this:
(pasting in code mode causes Stack Overflow to fix the formatting issue)
The exact string produced is:
'USAGE:\n play [FLAGS]\n\nFLAGS:\n -h, --help Prints help information\n -s, --scoring-method Very very very very very very long long long long long help help help help help message\n message message message message message message\n -V, --version Prints version information'
Clap has a max_term_width that it uses to wrap the text that it outputs. As mentioned in that documentation, it defaults to 120 characters, which is why you see the text split eventually, but not where you'd hoped.
If you have a certain width that you'd like to set, you can use set_term_width with a specific value. Alternatively, you can use a crate like terminal_size to get the size of the current terminal, and use that to set the width for clap to use.
As a full example:
extern crate clap;
extern crate terminal_size;
use clap::{App, Arg};
use terminal_size::{terminal_size, Width};
fn main() {
let args = App::new("test")
.set_term_width(if let Some((Width(w), _)) = terminal_size() { w as usize } else { 120 })
.arg(Arg::with_name("scoring_method")
.short("s")
.long("scoring-method")
.help("Very very very very very very long long long long long help help help help help message message message message message message message"))
.get_matches();
}
Today (Clap v4.0.26) you can enable the wrap_help feature. This takes a dependency on terminal_size internally.

Rust: use of partially moved values

In Rust 0.8:
struct TwoStr {
one: ~str,
two: ~str,
}
#[test]
fn test_contents() {
let strs = TwoStr {
one: ~"pillar",
two: ~"post",
};
assert_eq!(strs.one, ~"pillar");
assert_eq!(strs.two, ~"post");
}
The code won't even compile. The rust test thinks there's an error in the second assert_eq:
error: use of partially moved value: strs
It is somewhat counter-intuitive. I mean, whatever effects the first assert_eq may have, it should be well out of the scope when the execution reaches the second assert_eq. Unless of course, it does some spawn behind the scene. Does it?
If not, why this mysterious error? Hopefully there's no fundamental flaws in my understanding of Rust pointers.
In Rust 0.8, assert_eq! is defined as
macro_rules! assert_eq (
($given:expr , $expected:expr) => (
{
let given_val = $given;
let expected_val = $expected;
// check both directions of equality....
if !((given_val == expected_val) && (expected_val == given_val)) {
fail!(\"assertion failed: `(left == right) && (right == \
left)` (left: `%?`, right: `%?`)\", given_val, expected_val);
}
}
)
)
Note here that it moves both arguments into local let-bindings given_val and expected_val. This is what is causing your error.
In current master, this has been fixed. assert_eq! now takes references to the arguments:
macro_rules! assert_eq (
($given:expr , $expected:expr) => (
{
let given_val = &($given);
let expected_val = &($expected);
// check both directions of equality....
if !((*given_val == *expected_val) &&
(*expected_val == *given_val)) {
fail!("assertion failed: `(left == right) && (right == left)` \
(left: `{:?}`, right: `{:?}`)", *given_val, *expected_val)
}
}
)
)
This means that it no longer moves its arguments, which fixes your error.
If you need to stick with rust 0.8, you can change this to using assert!() instead and do the comparison directly, which will avoid the move. But my recommendation is to upgrade to latest master.
It works on Git master. It must be a bug that was fixed after 0.8 was cut. In general, the released versions are not constrained to be particularly stable before release - they're essentially just snapshots.
The definition of the assert_eq! macro, in libsyntax, takes references to the arguments, so the arguments should not be moved and you can use them after you call the macro.
If you do find another bug, try to compile master if you can, or just make a new issue.

Resources