pacak / bpaf

Command line parser with applicative interface
337 stars 17 forks source link

Format control over bpaf footer? #339

Open VorpalBlade opened 7 months ago

VorpalBlade commented 7 months ago

I cannot for the life of be figure out how to control newlines in the footer of --help output. What I'm trying to achieve is something like:

[...]
    -t, --style=STYLE      Style of generated modify script [path, path-tmpl (default), src]
[...]
    -h, --help             Prints help information
    -V, --version          Prints version information

The --style flag controls how the script that --add generates looks:

* path: chezmoi_modify_manager is searched for in PATH
  (modify_ script is not templated for best performance)
* path-tmpl: chezmoi_modify_manager is searched for in PATH
  (modify_ script is templated for your convenience)
* src: Program is in .utils of chezmoi source state
  (modify_ script is always templated) 

However the best I have managed so far is:

The --style flag controls how the script that --add generates looks:
* path: chezmoi_modify_manager is searched for in PATH (modify_ script is not templated for best performance)
* path-tmpl: chezmoi_modify_manager is searched for in PATH (modify_ script is templated for your convenience)
* src: Program is in .utils of chezmoi source state (modify_ script is always templated) 

This was done using the following code:

// Extra traits from strum
#[derive(
    Debug, Eq, PartialEq, EnumString, Clone, Copy, EnumIter, EnumMessage, Display, IntoStaticStr,
)]
pub enum Style {
    /// chezmoi_modify_manager is searched for in PATH (modify_ script is not templated for best performance)
    #[strum(serialize = "path")]
    InPath,
    /// chezmoi_modify_manager is searched for in PATH (modify_ script is templated for your convenience)
    #[strum(serialize = "path-tmpl")]
    InPathTmpl,
    /// Program is in .utils of chezmoi source state (modify_ script is always templated)
    #[strum(serialize = "src")]
    InSrc,
}

struct StyleFooter();

impl From<StyleFooter> for bpaf::Doc {
    fn from(_value: StyleFooter) -> Self {
        let mut doc = Self::default();
        doc.text("The --style flag controls how the script that --add generates looks:\n");
        for s in Style::iter()
        {
            doc.text(&format!(" * {}: {}\n", s, s.get_documentation().unwrap()));
        }
        doc
    }
}

pub fn parse_args() -> ChmmArgs {
    chmm_args()
    .footer(StyleFooter())
    .run()
}

If I change to add in the extra newlines, bpaf just eats half my output:

The --style flag controls how the script that --add generates looks:
* path: chezmoi_modify_manager is searched for in PATH (modify_ script is not templated for best performance)

(the rest is missing)

If I don't really care about generating markdown or man pages, is there a "do as I say" escape hatch?

VorpalBlade commented 7 months ago

(Oh another thing, I thought if I passed something that was Into<Doc> it would lazily evaluated only if the help text is produced. This is not the case unfortunately it seems after reading the code.

Since I'm working on optimising the runtime as much as possible for my program (and total runtime for a single invocation is 1-2 ms, but it is typically called many times by another program) I might look for an alternative solution, such as just providing a link to online docs, or a custom written man page.

Though if the formatting issue can be solved it doesn't seem like the overhead of doing this eagerly is actually very much currently when I check with hyperfine.)

pacak commented 7 months ago

This should help with forcing linebreaks

pub enum Style {
    /// chezmoi_modify_manager is searched for in PATH 
    ///  (modify_ script is not templated for best performance) // <- note extra space
    #[strum(serialize = "path")]
    InPath,
}

There's no escape hatches other than that.

For style - this is mostly useful if you want to use different styles - bold/emphasis, etc. Just to get line breaks you should be able to use line that starts with a single space. If you pass static str - it should have no extra runtime cost.

As for performance - I don't think I did any measurements, just making sure I'm not doing anything stupid, I guess I'll take a look.

VorpalBlade commented 7 months ago

Hm, doesn't markdown support having a trailing \ instead of a space? I tried that, but it didn't work. I have my editor set to strip trailing WS in all my projects, so I really don't like that one.

pacak commented 7 months ago
///  (modify_ script is not templated for best performance)
    ^--- I was talking about this space :)
VorpalBlade commented 7 months ago

Aha! Thanks.

I got a good enough output now, but the trick with leading spaces should probably be documented somewhere on https://docs.rs/bpaf/latest/bpaf/struct.Doc.html

pacak commented 7 months ago

For combinatoric API it's .help("hello\n word") <- space at the beginning of the line.

pacak commented 7 months ago

but the trick with leading spaces should probably be documented somewhere

My bad. It is documented in a few other places, I was sure I mentioned it there too... Will fix.

pacak commented 7 months ago

I documented it in most of the places that existed before Doc, like here: https://docs.rs/bpaf/latest/bpaf/params/struct.NamedArg.html#method.help

Doc is a recent addition...

VorpalBlade commented 7 months ago

Yes, the issue with docs.rs for bpaf (and other large crates) is that the search function isn't full-text from what I can tell. It only finds types/symbols/etc. So it is very hard to find these (or search in the tutorial pages etc)