Skip to content
This repository has been archived by the owner on Jan 1, 2022. It is now read-only.

--help message randomly show and hide docs lines #232

Open
2 tasks done
epage opened this issue Dec 6, 2021 · 5 comments
Open
2 tasks done

--help message randomly show and hide docs lines #232

epage opened this issue Dec 6, 2021 · 5 comments

Comments

@epage
Copy link
Owner

epage commented Dec 6, 2021

Issue by marcospb19
Tuesday Nov 02, 2021 at 17:37 GMT
Originally opened as clap-rs/clap#2983


Please complete the following tasks

  • I have searched the discussions
  • I have searched the existing issues

Rust Version

rustc 1.58.0-nightly (e249ce6b2 2021-10-30)

Clap Version

3.0.0-beta.5

Minimal reproducible code

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(version, about)]
pub struct Opts {
    #[clap(subcommand)]
    pub cmd: Subcommand,
}

// Ouch commands:
// - `compress`
// - `decompress`
// - `list`
//
// Clap commands:
//  - `help`
/// Repository: https://github.com/ouch-org/ouch
#[derive(Parser, PartialEq, Eq, Debug)]
pub enum Subcommand {
    Compress { output: String },
}

fn main() {
    let _ = Opts::parse();
}

Steps to reproduce the bug with the above code

cargo run -q -- --help

Actual Behaviour

This is a tricky one, if you copy my code example, here is the --help message:

teste 0.1.0



USAGE:
    teste <SUBCOMMAND>

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

SUBCOMMANDS:
    compress    
    help        Print this message or the help of the given subcommand(s)

So, that Repository line does not appear in the help message 👎, but if we change all the // comments to be /// (to three slashes):

teste 0.1.0

Ouch commands: - `compress` - `decompress` - `list`

Clap commands: - `help` Repository: https://github.com/ouch-org/ouch

USAGE:
    teste <SUBCOMMAND>

OPTIONS:
    -h, --help
            Print help information

    -V, --version
            Print version information

SUBCOMMANDS:
    compress
            
    help
            Print this message or the help of the given subcommand(s)

Now it appears!! 👍.

Now try deleting the empty comment "///\n". Does not appear anymore 👎.
Now you delete the #[clap(version, about)] line. Appears again 👍.

So, we went 👎 -> 👍 -> 👎 -> 👍 just by making small random changes.

Expected Behaviour

I assume it should be consistent and always show that repository line.

Additional Context

No response

Debug Output

With the starter code:

[            clap::build::app] 	App::_do_parse
[            clap::build::app] 	App::_build
[            clap::build::app] 	App::_propagate:teste
[            clap::build::app] 	App::_check_help_and_version
[            clap::build::app] 	App::_check_help_and_version: Building help subcommand
[            clap::build::app] 	App::_propagate_global_args:teste
[            clap::build::app] 	App::_derive_display_order:teste
[            clap::build::app] 	App::_derive_display_order:compress
[            clap::build::app] 	App::_derive_display_order:help
[clap::build::app::debug_asserts] 	App::_debug_asserts
[clap::build::arg::debug_asserts] 	Arg::_debug_asserts:help
[clap::build::arg::debug_asserts] 	Arg::_debug_asserts:version
[         clap::parse::parser] 	Parser::get_matches_with
[         clap::parse::parser] 	Parser::_build
[         clap::parse::parser] 	Parser::_verify_positionals
[         clap::parse::parser] 	Parser::get_matches_with: Begin parsing 'RawOsStr("--help")' ([45, 45, 104, 101, 108, 112])
[         clap::parse::parser] 	Parser::get_matches_with: Positional counter...1
[         clap::parse::parser] 	Parser::get_matches_with: Low index multiples...false
[         clap::parse::parser] 	Parser::possible_subcommand: arg=RawOsStr("--help")
[         clap::parse::parser] 	Parser::get_matches_with: sc=None
[         clap::parse::parser] 	Parser::parse_long_arg
[         clap::parse::parser] 	Parser::parse_long_arg: cur_idx:=1
[         clap::parse::parser] 	Parser::parse_long_arg: Does it contain '='...
[         clap::parse::parser] 	No
[         clap::parse::parser] 	Parser::parse_long_arg: Found valid opt or flag '--help'
[         clap::parse::parser] 	Parser::check_for_help_and_version_str
[         clap::parse::parser] 	Parser::check_for_help_and_version_str: Checking if --RawOsStr("help") is help or version...
[         clap::parse::parser] 	Help
[         clap::parse::parser] 	Parser::get_matches_with: After parse_long_arg HelpFlag
[         clap::parse::parser] 	[         clap::parse::parser] 	Parser::use_long_help
Parser::help_err: use_long=false
[         clap::parse::parser] 	Parser::use_long_help
[          clap::output::help] 	Help::new
[          clap::output::help] 	Help::write_help
[          clap::output::help] 	should_show_arg: use_long=false, arg=help
[          clap::output::help] 	Help::write_templated_help
[          clap::output::help] 	Help::write_before_help
[          clap::output::help] 	Help::write_bin_name
[         clap::output::usage] 	Usage::create_usage_no_title
[         clap::output::usage] 	Usage::create_help_usage; incl_reqs=true
[         clap::output::usage] 	Usage::get_required_usage_from: incls=[], matcher=false, incl_last=false
[         clap::output::usage] 	Usage::get_required_usage_from: unrolled_reqs={}
[         clap::output::usage] 	Usage::get_required_usage_from: ret_val=[]
[         clap::output::usage] 	Usage::needs_options_tag
[         clap::output::usage] 	Usage::needs_options_tag:iter: f=help
[         clap::output::usage] 	Usage::needs_options_tag:iter Option is built-in
[         clap::output::usage] 	Usage::needs_options_tag:iter: f=version
[         clap::output::usage] 	Usage::needs_options_tag:iter Option is built-in
[         clap::output::usage] 	Usage::needs_options_tag: [OPTIONS] not required
[         clap::output::usage] 	Usage::create_help_usage: usage=teste <SUBCOMMAND>
[          clap::output::help] 	Help::write_all_args
[          clap::output::help] 	should_show_arg: use_long=false, arg=help
[          clap::output::help] 	should_show_arg: use_long=false, arg=version
[          clap::output::help] 	Help::write_args
[          clap::output::help] 	should_show_arg: use_long=false, arg=help
[          clap::output::help] 	Help::write_args: Current Longest...2
[          clap::output::help] 	Help::write_args: New Longest...6
[          clap::output::help] 	should_show_arg: use_long=false, arg=version
[          clap::output::help] 	Help::write_args: Current Longest...6
[          clap::output::help] 	Help::write_args: New Longest...9
[          clap::output::help] 	should_show_arg: use_long=false, arg=help
[          clap::output::help] 	Help::spec_vals: a=--help
[          clap::output::help] 	should_show_arg: use_long=false, arg=version
[          clap::output::help] 	Help::spec_vals: a=--version
[          clap::output::help] 	Help::spec_vals: a=--help
[          clap::output::help] 	Help::short
[          clap::output::help] 	Help::long
[          clap::output::help] 	Help::val: arg=help
[          clap::output::help] 	Help::val: Has switch...
[          clap::output::help] 	Yes
[          clap::output::help] 	Help::val: nlh...false
[          clap::output::help] 	Help::help
[          clap::output::help] 	Help::help: Next Line...false
[          clap::output::help] 	Help::help: Too long...
[          clap::output::help] 	No
[          clap::output::help] 	Help::spec_vals: a=--version
[          clap::output::help] 	Help::short
[          clap::output::help] 	Help::long
[          clap::output::help] 	Help::val: arg=version
[          clap::output::help] 	Help::val: Has switch...
[          clap::output::help] 	Yes
[          clap::output::help] 	Help::val: nlh...false
[          clap::output::help] 	Help::help
[          clap::output::help] 	Help::help: Next Line...false
[          clap::output::help] 	Help::help: Too long...
[          clap::output::help] 	No
[          clap::output::help] 	Help::write_subcommands
[          clap::output::help] 	Help::write_subcommands longest = 8
[          clap::output::help] 	Help::sc_spec_vals: a=compress
[          clap::output::help] 	Help::sc_spec_vals: a=help
[          clap::output::help] 	Help::write_subcommand
[          clap::output::help] 	Help::sc_spec_vals: a=compress
[          clap::output::help] 	Help::help
[          clap::output::help] 	Help::help: Next Line...false
[          clap::output::help] 	Help::help: Too long...
[          clap::output::help] 	No
[          clap::output::help] 	Help::write_subcommand
[          clap::output::help] 	Help::sc_spec_vals: a=help
[          clap::output::help] 	Help::help
[          clap::output::help] 	Help::help: Next Line...false
[          clap::output::help] 	Help::help: Too long...
[          clap::output::help] 	No
[          clap::output::help] 	Help::write_after_help
[           clap::output::fmt] 	is_a_tty: stderr=false
@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by marcospb19
Tuesday Nov 02, 2021 at 17:38 GMT


Sent here by @epage, issue happened in this piece of code in one of my projects.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by epage
Tuesday Nov 02, 2021 at 17:44 GMT


Now try deleting the empty comment "///\n". Does not appear anymore

Which empty comment?

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by marcospb19
Tuesday Nov 02, 2021 at 17:47 GMT


(first step)

// Ouch commands:
// - `compress`
// - `decompress`
// - `list`
//
// Clap commands:
//  - `help`
/// Repository: https://github.com/ouch-org/ouch

(second step)

/// Ouch commands:
/// - `compress`
/// - `decompress`
/// - `list`
///
/// Clap commands:
///  - `help`
/// Repository: https://github.com/ouch-org/ouch

@epage then you delete that "///" blank line at the middle, leaving this:

(third step)

/// Ouch commands:
/// - `compress`
/// - `decompress`
/// - `list`
/// Clap commands:
///  - `help`
/// Repository: https://github.com/ouch-org/ouch

Sorry for the confusion, this bug has too many steps to reproduce, maybe I even reported more than one bug at the same time.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by epage
Tuesday Nov 02, 2021 at 18:06 GMT


Ok, let's break down all of the parts

  • We treat everything in the doc comment until the first blank line as part of the about, which is why the blank line is important
  • If there is no blank line, we do not populate long_about and the last about wins, which is from the Cargo.toml in this case
  • When there is a blank line, we set long_about but nothing after it overrides it, so it wins
  • Ideally, flatten and subcommand would not cause us to pull in their doc comments (see Args/Subcommand app settings shouldn't impact what they are flattened into clap-rs/clap#2894)

Ok, I think I covered it all

The challenge is in solutions because we can't introspect across items

  • #2894 which is big and might have some negative side effects
  • We could always set long_about but we do some conditional logic based on if long_about is used anywhere. We'd have to dig in to see what the consequences would be
  • Like with help_heading, we could allow users to set long_about (and more) to default. clap_derive could do this wherever it currently only sets about.

@epage
Copy link
Owner Author

epage commented Dec 6, 2021

Comment by epage
Tuesday Nov 02, 2021 at 18:16 GMT


Looks like if we always set long_about, it'll just cause long help to show up in errors, which seems pretty easy to do

    fn help_err(&self, mut use_long: bool) -> ClapError {
        debug!(
            "Parser::help_err: use_long={:?}",
            use_long && self.use_long_help()
        );

        use_long = use_long && self.use_long_help();
        ...
    }

    ...

    fn use_long_help(&self) -> bool {
        debug!("Parser::use_long_help");
        // In this case, both must be checked. This allows the retention of
        // original formatting, but also ensures that the actual -h or --help
        // specified by the user is sent through. If HiddenShortHelp is not included,
        // then items specified with hidden_short_help will also be hidden.
        let should_long = |v: &Arg| {
            v.long_about.is_some()
                || v.is_set(ArgSettings::HiddenLongHelp)
                || v.is_set(ArgSettings::HiddenShortHelp)
        };

        self.app.long_about.is_some()
            || self.app.before_long_help.is_some()
            || self.app.after_long_help.is_some()
            || self.app.args.args().any(should_long)
            || self.app.subcommands.iter().any(|s| s.long_about.is_some())
    }
  • That && is important, it means we will only do this if something else is also requesting long help
  • It looks like the only end-user consequence of long_about being set to about is the long help cases (like --help) we'll have the about on the next line rather than doing them all same-line with alignment
    • All other consequences can only be hit when long about is used, like choosing what is hidden in short vs long

This might be a good enough workaround until we can get a better solution.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant