Repository: pacak/bpaf
Pull request #101 (2022-10-09 14:07:01)

Color, hide_usage


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 3e0126d..d91b87a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "bpaf"
-version = "0.6.1"
+version = "0.7.0"
 edition = "2021"
 categories = ["command-line-interface"]
 description = "A simple Command Line Argument Parser with parser combinators"
@@ -15,6 +15,7 @@ exclude = [".github/workflows", "tarp.sh"]
 
 [dependencies]
 bpaf_derive = { path = "./bpaf_derive", version = "=0.3.1", optional = true }
+owo-colors = { version = "3.5.0", features = ["supports-colors"], optional = true }
 
 [dev-dependencies]
 bpaf = { path = ".",  features = ["derive", "extradocs", "autocomplete"] }
@@ -30,6 +31,9 @@ derive = ["bpaf_derive"]
 extradocs = []
 batteries = []
 autocomplete = []
+color = ["owo-colors"]
+bright-color = ["color"]
+dull-color = ["color"]
 
 [[test]]
 name = "completion"
diff --git a/Changelog.md b/Changelog.md
index f5bd077..4c688bc 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -5,6 +5,8 @@
 - pure_with implementation
   thanks to @xitep
 - FromOsStr is replaced with magical uses of Any trait
+- hide_usage
+- bright-color and dull-color features
 
 # Migration guide 0.6.x -> 0.7.x
 1. Remove FromUtf8 annotations if you have any
diff --git a/README.md b/README.md
index ddfd9b2..1ce3142 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,9 @@ Lightweight and flexible command line argument parser with derive and combinator
  - [Derive tutorial][__link0]
  - [Combinatoric tutorial][__link1]
  - [Some very unusual cases][__link2]
- - [Batteries included][__link3]
- - [Q&A][__link4]
+ - [Applicative functors? What is it all about][__link3]
+ - [Batteries included][__link4]
+ - [Q&A][__link5]
 
 
 ## Quick start, derive edition
@@ -197,9 +198,9 @@ But not this one:
 > 
 This set of restrictions allows `bpaf` to extract information about the structure of the computations to generate help, dynamic completion and overall results in less confusing enduser experience
 
-`bpaf` performs no parameter names validation, in fact having multiple parameters with the same name is fine and you can combine them as alternatives and performs no fallback other than [`fallback`][__link5]. You need to pay attention to the order of the alternatives inside the macro: parser that consumes the left most available argument on a command line wins, if this is the same - left most parser wins. So to parse a parameter `--test` that can be both [`switch`][__link6] and [`argument`][__link7] you should put the argument one first.
+`bpaf` performs no parameter names validation, in fact having multiple parameters with the same name is fine and you can combine them as alternatives and performs no fallback other than [`fallback`][__link6]. You need to pay attention to the order of the alternatives inside the macro: parser that consumes the left most available argument on a command line wins, if this is the same - left most parser wins. So to parse a parameter `--test` that can be both [`switch`][__link7] and [`argument`][__link8] you should put the argument one first.
 
-You must place [`positional`][__link8] items at the end of a structure in derive API or consume them as last arguments in derive API.
+You must place [`positional`][__link9] items at the end of a structure in derive API or consume them as last arguments in derive API.
 
 
 ## Dynamic shell completion
@@ -214,7 +215,7 @@ You must place [`positional`][__link8] items at the end of a structure in derive
 	```
 	
 	
- 1. Decorate [`argument`][__link9] and [`positional`][__link10] parsers with [`complete`][__link11] to autocomplete argument values
+ 1. Decorate [`argument`][__link10] and [`positional`][__link11] parsers with [`complete`][__link12] to autocomplete argument values
 	
 	
  1. Depending on your shell generate appropriate completion file and place it to whereever your shell is going to look for it, name of the file should correspond in some way to name of your program. Consult manual for your shell for the location and named conventions:
@@ -277,7 +278,7 @@ $ cargo run --example example_name
 
 ## Testing your own parsers
 
-You can test your own parsers to maintain compatibility or simply checking expected output with [`run_inner`][__link13]
+You can test your own parsers to maintain compatibility or simply checking expected output with [`run_inner`][__link14]
 
 
 ```rust
@@ -317,19 +318,30 @@ Usage --user <ARG>
  - `autocomplete`: enables support for shell autocompletion. Disabled by default.
 	
 	
+ - `bright-color`, `dull-color`: use more colors when printing `--help` and such. Enabling either color feature adds some extra dependencies and might raise MRSV. If you are planning to use this feature in a published app - it’s best to expose them as feature flags:
+	
+	
+	```toml
+	[features]
+	bright-color = ["bpaf/bright-color"]
+	dull-color = ["bpaf/dull-color"]
+	```
+	
+	
 
 
- [__cargo_doc2readme_dependencies_info]: ggGkYW0AYXSEG52uRQSwBdezG6GWW8ODAbr5G6KRmT_WpUB5G9hPmBcUiIp6YXKEGw52TMFIEQ0AG46Pg8Mnw4vqGw_DNHCWbX04G8RrPk7U2y7jYWSBgmRicGFmZTAuNi4w
- [__link0]: https://docs.rs/bpaf/0.6.0/bpaf/?search=_derive_tutorial
- [__link1]: https://docs.rs/bpaf/0.6.0/bpaf/?search=_combinatoric_tutorial
- [__link10]: https://docs.rs/bpaf/0.6.0/bpaf/?search=params::positional
- [__link11]: https://docs.rs/bpaf/0.6.0/bpaf/?search=bpaf::Parser::complete
- [__link13]: https://docs.rs/bpaf/0.6.0/bpaf/?search=info::OptionParser::run_inner
- [__link2]: https://docs.rs/bpaf/0.6.0/bpaf/?search=_unusual
- [__link3]: https://docs.rs/bpaf/0.6.0/bpaf/?search=batteries
- [__link4]: https://github.com/pacak/bpaf/discussions/categories/q-a
- [__link5]: https://docs.rs/bpaf/0.6.0/bpaf/?search=bpaf::Parser::fallback
- [__link6]: https://docs.rs/bpaf/0.6.0/bpaf/?search=parsers::NamedArg::switch
- [__link7]: https://docs.rs/bpaf/0.6.0/bpaf/?search=parsers::NamedArg::argument
- [__link8]: https://docs.rs/bpaf/0.6.0/bpaf/?search=params::positional
- [__link9]: https://docs.rs/bpaf/0.6.0/bpaf/?search=parsers::NamedArg::argument
+ [__cargo_doc2readme_dependencies_info]: ggGkYW0AYXSEG52uRQSwBdezG6GWW8ODAbr5G6KRmT_WpUB5G9hPmBcUiIp6YXKEG2QV09F__ZfbG2mHV-IJTiKrG00wy0_0pJVAG_GeoKpmAnK1YWSBgmRicGFmZTAuNy4w
+ [__link0]: https://docs.rs/bpaf/0.7.0/bpaf/?search=_derive_tutorial
+ [__link1]: https://docs.rs/bpaf/0.7.0/bpaf/?search=_combinatoric_tutorial
+ [__link10]: https://docs.rs/bpaf/0.7.0/bpaf/?search=parsers::NamedArg::argument
+ [__link11]: https://docs.rs/bpaf/0.7.0/bpaf/?search=params::positional
+ [__link12]: https://docs.rs/bpaf/0.7.0/bpaf/?search=bpaf::Parser::complete
+ [__link14]: https://docs.rs/bpaf/0.7.0/bpaf/?search=info::OptionParser::run_inner
+ [__link2]: https://docs.rs/bpaf/0.7.0/bpaf/?search=_unusual
+ [__link3]: https://docs.rs/bpaf/0.7.0/bpaf/?search=_applicative
+ [__link4]: https://docs.rs/bpaf/0.7.0/bpaf/?search=batteries
+ [__link5]: https://github.com/pacak/bpaf/discussions/categories/q-a
+ [__link6]: https://docs.rs/bpaf/0.7.0/bpaf/?search=bpaf::Parser::fallback
+ [__link7]: https://docs.rs/bpaf/0.7.0/bpaf/?search=parsers::NamedArg::switch
+ [__link8]: https://docs.rs/bpaf/0.7.0/bpaf/?search=parsers::NamedArg::argument
+ [__link9]: https://docs.rs/bpaf/0.7.0/bpaf/?search=params::positional
diff --git a/bpaf_cauwugo/Cargo.toml b/bpaf_cauwugo/Cargo.toml
index 33bcd3d..855c624 100644
--- a/bpaf_cauwugo/Cargo.toml
+++ b/bpaf_cauwugo/Cargo.toml
@@ -12,7 +12,7 @@ description = "A cargo frontend with dynamic completion"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bpaf = { version = "0.6.1", path = "../", features = ["derive", "autocomplete"] }
+bpaf = { version = "0.7.0", path = "../", features = ["derive", "autocomplete"] }
 cargo_metadata = "0.15.0"
 once_cell = "1.13.1"
 
diff --git a/bpaf_derive/src/field.rs b/bpaf_derive/src/field.rs
index 076a410..fc7558a 100644
--- a/bpaf_derive/src/field.rs
+++ b/bpaf_derive/src/field.rs
@@ -49,6 +49,7 @@ enum PostprAttr {
     FallbackWith(Span, Box<Expr>),
     Complete(Span, Ident),
     Hide(Span),
+    HideUsage(Span),
     GroupHelp(Span, Box<Expr>),
     Catch(Span),
 }
@@ -65,6 +66,7 @@ impl PostprAttr {
             | PostprAttr::FallbackWith(..)
             | PostprAttr::Complete(..)
             | PostprAttr::Hide(..)
+            | PostprAttr::HideUsage(..)
             | PostprAttr::Catch(..)
             | PostprAttr::GroupHelp(..) => true,
         }
@@ -81,6 +83,7 @@ impl PostprAttr {
             | PostprAttr::FallbackWith(span, _)
             | PostprAttr::Complete(span, _)
             | PostprAttr::Hide(span)
+            | PostprAttr::HideUsage(span)
             | PostprAttr::Catch(span)
             | PostprAttr::GroupHelp(span, _) => *span,
         }
@@ -202,6 +205,7 @@ impl ToTokens for PostprAttr {
             PostprAttr::Fallback(_span, v) => quote!(fallback(#v)),
             PostprAttr::FallbackWith(_span, v) => quote!(fallback_with(#v)),
             PostprAttr::Hide(_span) => quote!(hide()),
+            PostprAttr::HideUsage(_span) => quote!(hide_usage()),
             PostprAttr::Catch(_span) => quote!(catch()),
             PostprAttr::Complete(_span, f) => quote!(complete(#f)),
             PostprAttr::GroupHelp(_span, m) => quote!(group_help(#m)),
diff --git a/bpaf_derive/src/field/named_field.rs b/bpaf_derive/src/field/named_field.rs
index cd6c2a1..f803612 100644
--- a/bpaf_derive/src/field/named_field.rs
+++ b/bpaf_derive/src/field/named_field.rs
@@ -270,6 +270,9 @@ impl Field {
                     } else if keyword == "hide" {
                         check_stage(&mut stage, 4, &keyword)?;
                         res.postpr.push(PostprAttr::Hide(span));
+                    } else if keyword == "hide_usage" {
+                        check_stage(&mut stage, 4, &keyword)?;
+                        res.postpr.push(PostprAttr::HideUsage(span));
                     } else if keyword == "catch" {
                         check_stage(&mut stage, 4, &keyword)?;
                         res.postpr.push(PostprAttr::Catch(span));
diff --git a/docs/build.rs b/docs/build.rs
index d9442b8..449cbed 100644
--- a/docs/build.rs
+++ b/docs/build.rs
@@ -150,7 +150,10 @@ fn import_example<P: AsRef<Path>>(path: P) -> Result<(String, String), Box<dyn E
         writeln!(t_r, "}}")?;
 
         writeln!(t_d, "<details>")?;
-        writeln!(t_d, "<summary>Combinatoric usage</summary>")?;
+        writeln!(
+            t_d,
+            "<summary style=\"display: list-item;\">Combinatoric usage</summary>"
+        )?;
         writeln!(t_d)?;
         writeln!(t_d, "```no_run")?;
         include_file(&mut t_d, path.join("combine.rs"))?;
@@ -170,7 +173,10 @@ fn import_example<P: AsRef<Path>>(path: P) -> Result<(String, String), Box<dyn E
         writeln!(t_r, "}}")?;
 
         writeln!(t_d, "<details>")?;
-        writeln!(t_d, "<summary>Derive usage</summary>")?;
+        writeln!(
+            t_d,
+            "<summary style=\"display: list-item;\">Derive usage</summary>"
+        )?;
         writeln!(t_d)?;
         writeln!(t_d, "```no_run")?;
         include_file(&mut t_d, path.join("derive.rs"))?;
@@ -198,7 +204,10 @@ fn import_example<P: AsRef<Path>>(path: P) -> Result<(String, String), Box<dyn E
     }
 
     writeln!(t_d, "<details>")?;
-    writeln!(t_d, "<summary>Examples</summary>")?;
+    writeln!(
+        t_d,
+        "<summary style=\"display: list-item;\">Examples</summary>"
+    )?;
     writeln!(t_d)?;
     for sample in &cases {
         writeln!(t_d, "\n{}", sample.descr.trim())?;
diff --git a/docs/src/hide_usage/cases.txt b/docs/src/hide_usage/cases.txt
new file mode 100644
index 0000000..54f735d
--- /dev/null
+++ b/docs/src/hide_usage/cases.txt
@@ -0,0 +1,14 @@
+? `hide_usage` doesn't change the parsing behavior in any way
+> --argument 32
+OK
+Options { argument: 32, switch: false }
+
+? It hides the inner parser from usage line, but not from the rest of the help or completion
+> --help
+Stdout
+Usage: [--argument ARG]
+
+Available options:
+        --argument <ARG>  important argument
+        --switch          not that important switch
+    -h, --help            Prints help information
diff --git a/docs/src/hide_usage/combine.rs b/docs/src/hide_usage/combine.rs
new file mode 100644
index 0000000..1ba2c7f
--- /dev/null
+++ b/docs/src/hide_usage/combine.rs
@@ -0,0 +1,20 @@
+use bpaf::*;
+//
+#[allow(dead_code)]
+#[derive(Debug, Clone)]
+pub struct Options {
+    argument: u32,
+    switch: bool,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let argument = long("argument")
+        .help("important argument")
+        .argument("ARG")
+        .fallback(30);
+    let switch = long("switch")
+        .help("not that important switch")
+        .switch()
+        .hide_usage();
+    construct!(Options { argument, switch }).to_options()
+}
diff --git a/docs/src/hide_usage/derive.rs b/docs/src/hide_usage/derive.rs
new file mode 100644
index 0000000..2ff3540
--- /dev/null
+++ b/docs/src/hide_usage/derive.rs
@@ -0,0 +1,13 @@
+//
+use bpaf::*;
+#[allow(dead_code)]
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+pub struct Options {
+    /// important argument
+    #[bpaf(fallback(30))]
+    argument: u32,
+    /// not that important switch
+    #[bpaf(hide_usage)]
+    switch: bool,
+}
diff --git a/src/color.rs b/src/color.rs
new file mode 100644
index 0000000..91472da
--- /dev/null
+++ b/src/color.rs
@@ -0,0 +1,79 @@
+#[cfg(feature = "bright-color")]
+macro_rules! w_section {
+    ($buf:ident, $pat:literal) => {
+        write!(
+            $buf,
+            "{}",
+            ::owo_colors::OwoColorize::if_supports_color(
+                &$pat,
+                ::owo_colors::Stream::Stdout,
+                ::owo_colors::OwoColorize::yellow,
+            )
+        )
+    };
+}
+
+#[cfg(not(feature = "bright-color"))]
+macro_rules! w_section {
+    ($buf:ident, $pat:literal) => {
+        write!(
+            $buf,
+            "{}",
+            ::owo_colors::OwoColorize::if_supports_color(
+                &$pat,
+                ::owo_colors::Stream::Stdout,
+                |s| ::owo_colors::OwoColorize::style(
+                    s,
+                    ::owo_colors::Style::new().bold().underline()
+                )
+            )
+        )
+    };
+}
+
+#[cfg(not(feature = "bright-color"))]
+macro_rules! w_err {
+    ($item:expr) => {
+        ::owo_colors::OwoColorize::if_supports_color(
+            &$item,
+            ::owo_colors::Stream::Stdout,
+            ::owo_colors::OwoColorize::bold,
+        )
+    };
+}
+
+#[cfg(feature = "bright-color")]
+macro_rules! w_err {
+    ($item:expr) => {
+        ::owo_colors::OwoColorize::if_supports_color(
+            &$item,
+            ::owo_colors::Stream::Stdout,
+            ::owo_colors::OwoColorize::red,
+        )
+    };
+}
+
+#[cfg(feature = "bright-color")]
+macro_rules! w_flag {
+    ($item:expr) => {
+        ::owo_colors::OwoColorize::if_supports_color(
+            &$item,
+            ::owo_colors::Stream::Stdout,
+            ::owo_colors::OwoColorize::green,
+        )
+    };
+}
+
+#[cfg(not(feature = "bright-color"))]
+macro_rules! w_flag {
+    ($item:expr) => {
+        ::owo_colors::OwoColorize::if_supports_color(
+            &$item,
+            ::owo_colors::Stream::Stdout,
+            ::owo_colors::OwoColorize::bold,
+        )
+    };
+}
+
+#[doc(hidden)]
+pub use owo_colors::set_override;
diff --git a/src/docs/adjacent_0.md b/src/docs/adjacent_0.md
index ef8c710..36f9921 100644
--- a/src/docs/adjacent_0.md
+++ b/src/docs/adjacent_0.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -42,7 +42,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -72,7 +72,7 @@ struct Multi {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 short flag `-m` takes 3 positional arguments: two integers and one floating point, order is
diff --git a/src/docs/adjacent_1.md b/src/docs/adjacent_1.md
index f3eb00a..ee40e77 100644
--- a/src/docs/adjacent_1.md
+++ b/src/docs/adjacent_1.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -42,7 +42,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Order of items within the rectangle is not significant and you can have several of them
diff --git a/src/docs/adjacent_2.md b/src/docs/adjacent_2.md
index 6726c17..647721d 100644
--- a/src/docs/adjacent_2.md
+++ b/src/docs/adjacent_2.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -51,7 +51,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 You can chain one or more commands, commands can be arbitrarily nested too
diff --git a/src/docs/adjacent_3.md b/src/docs/adjacent_3.md
index cafe2be..0bb490f 100644
--- a/src/docs/adjacent_3.md
+++ b/src/docs/adjacent_3.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use std::ffi::OsString;
@@ -30,7 +30,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 You can have as many items between `--exec` and `;` as you want, they all will be captured
diff --git a/src/docs/adjacent_4.md b/src/docs/adjacent_4.md
index 76c9b62..87c406b 100644
--- a/src/docs/adjacent_4.md
+++ b/src/docs/adjacent_4.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -37,7 +37,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Let's start simple - a single flag accepts a bunch of stuff, and eveything is present
diff --git a/src/docs/any.md b/src/docs/any.md
index ab26e44..ad49ea5 100644
--- a/src/docs/any.md
+++ b/src/docs/any.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use std::ffi::OsString;
@@ -26,7 +26,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use std::ffi::OsString;
@@ -50,7 +50,7 @@ fn not_help(s: &OsString) -> bool {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Capture `--turbo` flag for internal use and return everything else as is so it can be passed
diff --git a/src/docs/anywhere.md b/src/docs/anywhere.md
index 6665330..00b519c 100644
--- a/src/docs/anywhere.md
+++ b/src/docs/anywhere.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -49,7 +49,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 This example shows how to parse some very unusual options, same style as used by Xorg
diff --git a/src/docs/argument.md b/src/docs/argument.md
index f7e02e9..19e6b67 100644
--- a/src/docs/argument.md
+++ b/src/docs/argument.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -23,7 +23,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -44,7 +44,7 @@ pub struct Options {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Names for arguments could be short or long, and they can be parsed as mutiple different
diff --git a/src/docs/boxed.md b/src/docs/boxed.md
index 220361b..b250687 100644
--- a/src/docs/boxed.md
+++ b/src/docs/boxed.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -32,7 +32,7 @@ pub fn options() -> OptionParser<Command> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 You can make some dynamic things with bpaf, depending on a value of `sneaky` command `a` is
diff --git a/src/docs/cargo_helper.md b/src/docs/cargo_helper.md
index 6257ab1..3c8cc7d 100644
--- a/src/docs/cargo_helper.md
+++ b/src/docs/cargo_helper.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -23,7 +23,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -41,7 +41,7 @@ pub struct Options {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Let's say the goal is to parse an argument and a switch:
diff --git a/src/docs/catch.md b/src/docs/catch.md
index fa1cc04..e5cd2a9 100644
--- a/src/docs/catch.md
+++ b/src/docs/catch.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -18,7 +18,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -35,7 +35,7 @@ pub struct Options {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 No argument, both parsers succeed due to [`optional`](Parser::optional) alone
diff --git a/src/docs/command.md b/src/docs/command.md
index d15f29b..cecad79 100644
--- a/src/docs/command.md
+++ b/src/docs/command.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -39,7 +39,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -66,7 +66,7 @@ pub struct Options {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 In this example there's only one command and it is required, so is the argument inside of it
diff --git a/src/docs/dd.md b/src/docs/dd.md
index 6249bba..368e732 100644
--- a/src/docs/dd.md
+++ b/src/docs/dd.md
@@ -64,7 +64,7 @@ fn main() {
 
 ```
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 `dd` takes parameters in `name=value` shape
diff --git a/src/docs/find.md b/src/docs/find.md
index 3d21100..74fab83 100644
--- a/src/docs/find.md
+++ b/src/docs/find.md
@@ -111,7 +111,7 @@ fn main() {
 
 ```
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Usually `find` takes a path where to look, the rest is optional
diff --git a/src/docs/flag.md b/src/docs/flag.md
index d8674ca..2eaa61d 100644
--- a/src/docs/flag.md
+++ b/src/docs/flag.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -29,7 +29,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -50,7 +50,7 @@ pub enum Decision {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Presense of a long name is decoded into `Yes`
diff --git a/src/docs/hide_usage.md b/src/docs/hide_usage.md
new file mode 100644
index 0000000..ee80738
--- /dev/null
+++ b/src/docs/hide_usage.md
@@ -0,0 +1,67 @@
+<details>
+<summary style="display: list-item;">Combinatoric usage</summary>
+
+```no_run
+use bpaf::*;
+# #[allow(dead_code)]
+#[derive(Debug, Clone)]
+pub struct Options {
+    argument: u32,
+    switch: bool,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let argument = long("argument")
+        .help("important argument")
+        .argument("ARG")
+        .fallback(30);
+    let switch = long("switch")
+        .help("not that important switch")
+        .switch()
+        .hide_usage();
+    construct!(Options { argument, switch }).to_options()
+}
+```
+
+</details>
+<details>
+<summary style="display: list-item;">Derive usage</summary>
+
+```no_run
+# use bpaf::*;
+#[allow(dead_code)]
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+pub struct Options {
+    /// important argument
+    #[bpaf(fallback(30))]
+    argument: u32,
+    /// not that important switch
+    #[bpaf(hide_usage)]
+    switch: bool,
+}
+```
+
+</details>
+<details>
+<summary style="display: list-item;">Examples</summary>
+
+
+`hide_usage` doesn't change the parsing behavior in any way
+```console
+% app --argument 32
+Options { argument: 32, switch: false }
+```
+
+It hides the inner parser from usage line, but not from the rest of the help or completion
+```console
+% app --help
+Usage: [--argument ARG]
+
+Available options:
+        --argument <ARG>  important argument
+        --switch          not that important switch
+    -h, --help            Prints help information
+```
+
+</details>
diff --git a/src/docs/numeric_prefix.md b/src/docs/numeric_prefix.md
index a8bcce2..7070b06 100644
--- a/src/docs/numeric_prefix.md
+++ b/src/docs/numeric_prefix.md
@@ -24,7 +24,7 @@ fn main() {
 
 ```
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 If `bpaf` can parse first positional argument as number - it becomes a numeric prefix
diff --git a/src/docs/pos_adjacent.md b/src/docs/pos_adjacent.md
index 7a3421f..aeb1c06 100644
--- a/src/docs/pos_adjacent.md
+++ b/src/docs/pos_adjacent.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -30,7 +30,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 other than looking strange `banana()` should behave like a regular flag parser: banana - yes
diff --git a/src/docs/positional.md b/src/docs/positional.md
index 1c8678f..9a1ce17 100644
--- a/src/docs/positional.md
+++ b/src/docs/positional.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use std::{path::PathBuf, str::FromStr};
@@ -45,7 +45,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use std::{path::PathBuf, str::FromStr};
@@ -86,7 +86,7 @@ impl FromStr for Coin {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Positionals are consumed left to right, one at a time, no skipping unless the value is optional
diff --git a/src/docs/req_flag.md b/src/docs/req_flag.md
index 32f8cf5..190fb7e 100644
--- a/src/docs/req_flag.md
+++ b/src/docs/req_flag.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -33,7 +33,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -59,7 +59,7 @@ pub enum Decision {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 This example implements a tri-state switch: wether decision was made
diff --git a/src/docs/short_long_env.md b/src/docs/short_long_env.md
index fec8b2b..1c5d827 100644
--- a/src/docs/short_long_env.md
+++ b/src/docs/short_long_env.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -43,7 +43,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -65,7 +65,7 @@ pub struct Options {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 As usual switch is optional, arguments are required
diff --git a/src/docs/switch.md b/src/docs/switch.md
index 57f5a67..8669ca9 100644
--- a/src/docs/switch.md
+++ b/src/docs/switch.md
@@ -1,5 +1,5 @@
 <details>
-<summary>Combinatoric usage</summary>
+<summary style="display: list-item;">Combinatoric usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -21,7 +21,7 @@ pub fn options() -> OptionParser<Options> {
 
 </details>
 <details>
-<summary>Derive usage</summary>
+<summary style="display: list-item;">Derive usage</summary>
 
 ```no_run
 # use bpaf::*;
@@ -35,7 +35,7 @@ pub struct Options {
 
 </details>
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 Presense of a long name is decoded into `true`
diff --git a/src/docs/xorg.md b/src/docs/xorg.md
index 07b54bb..9ddf89a 100644
--- a/src/docs/xorg.md
+++ b/src/docs/xorg.md
@@ -77,7 +77,7 @@ fn main() {
 
 ```
 <details>
-<summary>Examples</summary>
+<summary style="display: list-item;">Examples</summary>
 
 
 `xorg` takes parameters in a few different ways, notably as a long name starting with plus or
diff --git a/src/info.rs b/src/info.rs
index 6f47eb1..89f23a1 100644
--- a/src/info.rs
+++ b/src/info.rs
@@ -9,7 +9,7 @@ use crate::{
 };
 
 /// Unsuccessful command line parsing outcome, internal representation
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub enum Error {
     /// Terminate and print this to stdout
     Stdout(String),
@@ -228,8 +228,7 @@ impl<T> OptionParser<T> {
     }
 
     /// Run subparser, implementation detail
-    #[doc(hidden)]
-    pub fn run_subparser(&self, args: &mut Args) -> Result<T, Error> {
+    pub(crate) fn run_subparser(&self, args: &mut Args) -> Result<T, Error> {
         let depth = args.depth;
         let res = self.inner.eval(args);
         if let Err(Error::Stdout(_)) = &res {
@@ -279,12 +278,12 @@ impl<T> OptionParser<T> {
 
         Err(report_missing_items(err))
     }
+
     /// Get first line of description if Available
     ///
     /// Used internally to avoid duplicating description for [`command`].
-    #[doc(hidden)]
     #[must_use]
-    pub fn short_descr(&self) -> Option<&'static str> {
+    pub(crate) fn short_descr(&self) -> Option<&'static str> {
         self.info.descr.and_then(|descr| descr.lines().next())
     }
 
@@ -605,7 +604,7 @@ fn perform_invariant_check(meta: &Meta, fresh: bool) {
                 perform_invariant_check(i, false);
             }
         }
-        Meta::Optional(x) | Meta::Many(x) | Meta::Decorated(x, _) => {
+        Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) | Meta::Decorated(x, _) => {
             perform_invariant_check(x, false);
         }
         Meta::Item(i) => match i {
@@ -625,13 +624,11 @@ impl Parser<ExtraParams> for ParseExtraParams {
         if let Ok(ok) = ParseExtraParams::help().eval(args) {
             return Ok(ok);
         }
-        let not_ok = Error::Stderr(String::from("Not a version or help flag"));
-        let ver = self.version.ok_or_else(|| not_ok.clone())?;
 
-        if let Ok(ok) = Self::ver(ver).eval(args) {
-            return Ok(ok);
+        match self.version {
+            Some(ver) => Self::ver(ver).eval(args),
+            None => Err(Error::Stderr(String::from("Not a version or help flag"))),
         }
-        Err(not_ok)
     }
 
     fn meta(&self) -> Meta {
diff --git a/src/item.rs b/src/item.rs
index 3048cdd..d9348d3 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -56,7 +56,7 @@ impl std::fmt::Display for Item {
                 metavar,
                 help: _,
                 strict: _,
-            } => write!(f, "<{}>", metavar),
+            } => metavar.fmt(f),
             Item::Command { .. } => write!(f, "COMMAND ..."),
             Item::Flag {
                 name,
diff --git a/src/lib.rs b/src/lib.rs
index 1644fd7..ffe0393 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -311,6 +311,29 @@
 //! - `batteries`: helpers implemented with public `bpaf` API. Enabled by default.
 //!
 //! - `autocomplete`: enables support for shell autocompletion. Disabled by default.
+//!
+//! - `bright-color`, `dull-color`: use more colors when printing `--help` and such. Enabling
+//!   either color feature adds some extra dependencies and might raise MRSV. If you are planning
+//!   to use this feature in a published app - it's best to expose them as feature flags:
+//!   ```toml
+//!   [features]
+//!   bright-color = ["bpaf/bright-color"]
+//!   dull-color = ["bpaf/dull-color"]
+//!   ```
+
+#[macro_use]
+#[cfg(feature = "color")]
+mod color;
+
+#[macro_use]
+#[cfg(not(feature = "color"))]
+mod no_color;
+
+#[cfg(feature = "color")]
+pub use color::set_override;
+
+#[cfg(not(feature = "color"))]
+pub use no_color::set_override;
 
 #[cfg(feature = "extradocs")]
 pub mod _applicative;
@@ -359,8 +382,8 @@ pub mod parsers {
 
 use structs::{
     ParseAdjacent, ParseAnywhere, ParseFail, ParseFallback, ParseFallbackWith, ParseGroupHelp,
-    ParseGuard, ParseHide, ParseMany, ParseMap, ParseOptional, ParseOrElse, ParsePure,
-    ParsePureWith, ParseSome, ParseWith,
+    ParseGuard, ParseHide, ParseHideUsage, ParseMany, ParseMap, ParseOptional, ParseOrElse,
+    ParsePure, ParsePureWith, ParseSome, ParseWith,
 };
 
 #[cfg(feature = "autocomplete")]
@@ -382,7 +405,6 @@ pub(self) use crate::parsers::NamedArg;
 #[cfg(feature = "bpaf_derive")]
 pub use bpaf_derive::Bpaf;
 mod from_os_str;
-pub use from_os_str::*;
 
 /// Compose several parsers to produce a single result
 ///
@@ -1361,6 +1383,21 @@ pub trait Parser<T> {
     }
     // }}}
 
+    /// Ignore this parser when generating usage line
+    ///
+    /// Parsers hidden from usage will still show up in available arguments list. Best used on
+    /// optional things that augment main application functionality but not define it. You might
+    /// use custom usage to indicate that some options are hidden
+    ///
+    #[doc = include_str!("docs/hide_usage.md")]
+    #[must_use]
+    fn hide_usage(self) -> ParseHideUsage<Self>
+    where
+        Self: Sized + Parser<T>,
+    {
+        ParseHideUsage { inner: self }
+    }
+
     // {{{ group_help
     /// Attach help message to a complex parser
     ///
diff --git a/src/meta.rs b/src/meta.rs
index ad6e9a1..306c49f 100644
--- a/src/meta.rs
+++ b/src/meta.rs
@@ -13,6 +13,7 @@ pub enum Meta {
     Many(Box<Meta>),
     Decorated(Box<Meta>, &'static str),
     Skip,
+    HideUsage(Box<Meta>),
 }
 
 impl std::fmt::Display for Meta {
@@ -51,6 +52,7 @@ impl Meta {
         to_usage_meta(self)
     }
 
+    /// collect different kinds of short names for disambiguation
     pub(crate) fn collect_shorts(&self, flags: &mut Vec<char>, args: &mut Vec<char>) {
         match self {
             Meta::And(xs) | Meta::Or(xs) => {
@@ -63,7 +65,7 @@ impl Meta {
                 Item::Flag { shorts, .. } => flags.extend(shorts),
                 Item::Argument { shorts, .. } => args.extend(shorts),
             },
-            Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _) => {
+            Meta::HideUsage(m) | Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _) => {
                 m.collect_shorts(flags, args);
             }
             Meta::Skip => {}
diff --git a/src/meta_help.rs b/src/meta_help.rs
index 5a87ea8..eb0a4d5 100644
--- a/src/meta_help.rs
+++ b/src/meta_help.rs
@@ -6,6 +6,19 @@ use crate::{
     Meta,
 };
 
+#[doc(hidden)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
+pub(crate) struct Metavar(&'static str);
+
+impl std::fmt::Display for Metavar {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        use std::fmt::Write;
+        f.write_char('<')?;
+        f.write_str(self.0)?;
+        f.write_char('>')
+    }
+}
+
 #[derive(Debug, Ord, PartialEq, PartialOrd, Eq, Copy, Clone)]
 pub(crate) enum HelpItem<'a> {
     Decor {
@@ -14,7 +27,7 @@ pub(crate) enum HelpItem<'a> {
     BlankDecor,
     Positional {
         strict: bool,
-        metavar: &'static str,
+        metavar: Metavar,
         help: Option<&'a str>,
     },
     Command {
@@ -28,7 +41,7 @@ pub(crate) enum HelpItem<'a> {
     },
     Argument {
         name: ShortLongHelp,
-        metavar: &'static str,
+        metavar: Metavar,
         env: Option<&'static str>,
         help: Option<&'a str>,
     },
@@ -66,7 +79,7 @@ impl<'a> HelpItems<'a> {
             } => {
                 if help.is_some() {
                     self.psns.push(HelpItem::Positional {
-                        metavar,
+                        metavar: Metavar(metavar),
                         help: help.as_deref(),
                         strict: *strict,
                     });
@@ -100,7 +113,7 @@ impl<'a> HelpItems<'a> {
                 shorts: _,
             } => self.flgs.push(HelpItem::Argument {
                 name: ShortLongHelp(*name),
-                metavar,
+                metavar: Metavar(metavar),
                 env: *env,
                 help: help.as_deref(),
             }),
@@ -114,7 +127,7 @@ impl<'a> HelpItems<'a> {
                     self.classify(x);
                 }
             }
-            Meta::Optional(x) | Meta::Many(x) => self.classify(x),
+            Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) => self.classify(x),
             Meta::Item(item) => self.classify_item(item),
 
             Meta::Decorated(m, help) => {
@@ -159,12 +172,31 @@ impl ShortLongHelp {
     }
 }
 
+pub(crate) struct Long<'a>(pub(crate) &'a str);
+impl std::fmt::Display for Long<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("--")?;
+        f.write_str(self.0)
+    }
+}
+
+pub(crate) struct Short(pub(crate) char);
+impl std::fmt::Display for Short {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        use std::fmt::Write;
+        f.write_char('-')?;
+        f.write_char(self.0)
+    }
+}
+
 impl std::fmt::Display for ShortLongHelp {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self.0 {
-            ShortLong::Short(short) => write!(f, "-{}", short),
-            ShortLong::Long(long) => write!(f, "    --{}", long),
-            ShortLong::ShortLong(short, long) => write!(f, "-{}, --{}", short, long),
+            ShortLong::Short(short) => write!(f, "{}", w_flag!(Short(short))),
+            ShortLong::Long(long) => write!(f, "    {}", w_flag!(Long(long))),
+            ShortLong::ShortLong(short, long) => {
+                write!(f, "{}, {}", w_flag!(Short(short)), w_flag!(Long(long)))
+            }
         }
     }
 }
@@ -173,14 +205,14 @@ impl std::fmt::Display for ShortLongHelp {
 impl std::fmt::Display for HelpItem<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            HelpItem::Flag { name, help: _ } => write!(f, "    {:#}", name),
+            HelpItem::Flag { name, help: _ } => write!(f, "    {}", name),
             HelpItem::Argument {
                 name,
                 metavar,
                 help,
                 env,
             } => {
-                write!(f, "    {:#} <{}>", name, metavar)?;
+                write!(f, "    {} {}", name, w_flag!(metavar))?;
 
                 let width = f.width().unwrap();
                 if let Some(env) = env {
@@ -207,9 +239,9 @@ impl std::fmt::Display for HelpItem<'_> {
                 strict,
             } => {
                 if *strict {
-                    write!(f, "    -- <{}>", metavar)
+                    write!(f, "    -- {}", w_flag!(metavar))
                 } else {
-                    write!(f, "    <{}>", metavar)
+                    write!(f, "    {}", w_flag!(metavar))
                 }
             }
             HelpItem::Command {
@@ -217,8 +249,8 @@ impl std::fmt::Display for HelpItem<'_> {
                 help: _,
                 short,
             } => match short {
-                Some(s) => write!(f, "    {}, {}", name, s),
-                None => write!(f, "    {}", name),
+                Some(s) => write!(f, "    {}, {}", w_flag!(name), w_flag!(s)),
+                None => write!(f, "    {}", w_flag!(name)),
             },
         }?;
 
@@ -248,7 +280,7 @@ impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
                 help,
                 strict,
             } => Self::Positional {
-                metavar,
+                metavar: Metavar(metavar),
                 strict: *strict,
                 help: help.as_deref(),
             },
@@ -278,7 +310,7 @@ impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
                 shorts: _,
             } => Self::Argument {
                 name: ShortLongHelp(*name),
-                metavar,
+                metavar: Metavar(metavar),
                 env: *env,
                 help: help.as_deref(),
             },
@@ -294,8 +326,8 @@ impl HelpItem<'_> {
         match self {
             HelpItem::Decor { .. } | HelpItem::BlankDecor { .. } => 0,
             HelpItem::Flag { name, .. } => name.full_width(),
-            HelpItem::Argument { name, metavar, .. } => name.full_width() + metavar.len() + 3,
-            HelpItem::Positional { metavar, .. } => metavar.len() + 2,
+            HelpItem::Argument { name, metavar, .. } => name.full_width() + metavar.0.len() + 3,
+            HelpItem::Positional { metavar, .. } => metavar.0.len() + 2,
             HelpItem::Command {
                 name, short: None, ..
             } => name.len(),
@@ -361,7 +393,7 @@ pub(crate) fn render_help(
             .map(HelpItem::full_width)
             .max()
             .unwrap_or(0);
-        write!(res, "\nAvailable positional items:\n")?;
+        w_section!(res, "\nAvailable positional items:\n")?;
         for i in &items.psns {
             write!(res, "{:padding$}\n", i, padding = max_width)?;
         }
@@ -374,14 +406,14 @@ pub(crate) fn render_help(
             .map(HelpItem::full_width)
             .max()
             .unwrap_or(0);
-        write!(res, "\nAvailable options:\n")?;
+        w_section!(res, "\nAvailable options:\n")?;
         for i in &items.flgs {
             write!(res, "{:padding$}\n", i, padding = max_width)?;
         }
     }
 
     if !items.cmds.is_empty() {
-        write!(res, "\nAvailable commands:\n")?;
+        w_section!(res, "\nAvailable commands:\n")?;
         let max_width = items
             .cmds
             .iter()
diff --git a/src/meta_usage.rs b/src/meta_usage.rs
index 81cc67f..dbd26b2 100644
--- a/src/meta_usage.rs
+++ b/src/meta_usage.rs
@@ -118,7 +118,7 @@ fn collect_usage_meta(meta: &Meta, is_pos: &mut bool) -> Option<UsageMeta> {
             }
         }
         Meta::Decorated(meta, _) => collect_usage_meta(meta, is_pos)?,
-        Meta::Skip => return None,
+        Meta::HideUsage(_) | Meta::Skip => return None,
         Meta::Item(i) => match i {
             Item::Positional {
                 metavar, strict, ..
diff --git a/src/meta_youmean.rs b/src/meta_youmean.rs
index 26e22e8..02ad5a5 100644
--- a/src/meta_youmean.rs
+++ b/src/meta_youmean.rs
@@ -1,6 +1,7 @@
 use crate::{
     args::Arg,
     item::{Item, ShortLong},
+    meta_help::{Long, Short},
     Args, Error, Meta,
 };
 
@@ -61,10 +62,10 @@ enum I<'a> {
 impl std::fmt::Debug for I<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            Self::ShortFlag(s) => write!(f, "flag: `-{}`", s),
-            Self::LongFlag(s) => write!(f, "flag: `--{}`", s),
-            Self::ShortCmd(s) => write!(f, "command alias: `{}`", s),
-            Self::LongCmd(s) => write!(f, "command: `{}`", s),
+            Self::ShortFlag(s) => write!(f, "flag: `{}`", w_err!(Short(*s))),
+            Self::LongFlag(s) => write!(f, "flag: `{}`", w_err!(Long(s))),
+            Self::ShortCmd(s) => write!(f, "command alias: `{}`", w_err!(s)),
+            Self::LongCmd(s) => write!(f, "command: `{}`", w_err!(s)),
         }
     }
 }
@@ -85,7 +86,11 @@ impl std::fmt::Display for I<'_> {
 fn ins(expected: I, actual: I, variants: &mut Vec<(usize, String)>) {
     variants.push((
         levenshtein(&expected.to_string(), &actual.to_string()),
-        format!("No such {:?}, did you mean `{}`?", actual, expected),
+        format!(
+            "No such {:?}, did you mean `{}`?",
+            actual,
+            w_flag!(expected)
+        ),
     ));
 }
 
@@ -127,7 +132,10 @@ fn inner(arg: &Arg, meta: &Meta, variants: &mut Vec<(usize, String)>) {
             }
         }
         Meta::Item(item) => inner_item(arg, item, variants),
-        Meta::Optional(meta) | Meta::Many(meta) | Meta::Decorated(meta, _) => {
+        Meta::HideUsage(meta)
+        | Meta::Optional(meta)
+        | Meta::Many(meta)
+        | Meta::Decorated(meta, _) => {
             inner(arg, meta, variants);
         }
         Meta::Skip => {}
diff --git a/src/no_color.rs b/src/no_color.rs
new file mode 100644
index 0000000..77fc239
--- /dev/null
+++ b/src/no_color.rs
@@ -0,0 +1,20 @@
+macro_rules! w_section {
+    ($buf:ident, $pat:literal) => {
+        write!($buf, "{}", &$pat,)
+    };
+}
+
+macro_rules! w_flag {
+    ($item:expr) => {
+        $item
+    };
+}
+
+macro_rules! w_err {
+    ($item:expr) => {
+        $item
+    };
+}
+
+#[doc(hidden)]
+pub fn set_override(_: bool) {}
diff --git a/src/params.rs b/src/params.rs
index 8adf014..083dc3e 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -60,8 +60,8 @@
 //!
 use std::{ffi::OsString, marker::PhantomData, str::FromStr};
 
-use super::{parse_os_str, Args, Error, OptionParser, Parser};
-use crate::{args::Arg, item::ShortLong, Item, Meta};
+use super::{Args, Error, OptionParser, Parser};
+use crate::{args::Arg, from_os_str::parse_os_str, item::ShortLong, Item, Meta};
 
 /// A named thing used to create [`flag`](NamedArg::flag), [`switch`](NamedArg::switch) or
 /// [`argument`](NamedArg::argument)
diff --git a/src/structs.rs b/src/structs.rs
index a8fd1fd..b199191 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -140,6 +140,25 @@ where
     }
 }
 
+/// Parser that hides inner parser from usage line
+///
+/// Otherwise the behavior is unchanged
+pub struct ParseHideUsage<P> {
+    pub(crate) inner: P,
+}
+impl<T, P> Parser<T> for ParseHideUsage<P>
+where
+    P: Parser<T>,
+{
+    fn eval(&self, args: &mut Args) -> Result<T, Error> {
+        self.inner.eval(args)
+    }
+
+    fn meta(&self) -> Meta {
+        Meta::HideUsage(Box::new(self.inner.meta()))
+    }
+}
+
 /// Parser that tries to either of two parsers and uses one that succeeeds, created with
 /// [`Parser::or_else`].
 pub struct ParseOrElse<A, B> {


Test patch:
diff --git a/bpaf_derive/src/field_tests.rs b/bpaf_derive/src/field_tests.rs
index 0883744..0ec0ce5 100644
--- a/bpaf_derive/src/field_tests.rs
+++ b/bpaf_derive/src/field_tests.rs
@@ -572,3 +572,15 @@ fn unit_fields_are_required() {
     };
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
+
+#[test]
+fn hide_usage() {
+    let input: NamedField = parse_quote! {
+        #[bpaf(hide_usage)]
+        field: u32
+    };
+    let output = quote! {
+        ::bpaf::long("field").argument::<u32>("ARG").hide_usage()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
diff --git a/tests/derive.rs b/tests/derive.rs
index 9a78140..894f591 100644
--- a/tests/derive.rs
+++ b/tests/derive.rs
@@ -3,6 +3,7 @@ use bpaf::*;
 
 #[test]
 fn help_with_default_parse() {
+    set_override(false);
     use bpaf::Parser;
     #[derive(Debug, Clone, Bpaf)]
     enum Action {
diff --git a/tests/help_format.rs b/tests/help_format.rs
index 31feb87..76d653f 100644
--- a/tests/help_format.rs
+++ b/tests/help_format.rs
@@ -2,6 +2,7 @@ use bpaf::*;
 
 #[test]
 fn decorations() {
+    set_override(false);
     let p = short('p')
         .long("parser")
         .env("BPAF_VARIABLE")
diff --git a/tests/meta_usage.rs b/tests/meta_usage.rs
index ba88c38..251eb84 100644
--- a/tests/meta_usage.rs
+++ b/tests/meta_usage.rs
@@ -173,6 +173,7 @@ fn required_or_many() {
 
 #[test]
 fn no_actual_arguments_also_works() {
+    set_override(false);
     let parser = pure(true).to_options();
 
     let r = parser
diff --git a/tests/meta_youmean.rs b/tests/meta_youmean.rs
index 9f7d9f9..3013623 100644
--- a/tests/meta_youmean.rs
+++ b/tests/meta_youmean.rs
@@ -2,6 +2,7 @@ use bpaf::*;
 
 #[test]
 fn ambiguity() {
+    set_override(false);
     #[derive(Debug, Clone)]
     enum A {
         V(Vec<bool>),
@@ -27,6 +28,7 @@ fn ambiguity() {
 
 #[test]
 fn short_cmd() {
+    set_override(false);
     let parser = long("alpha")
         .req_flag(())
         .to_options()
diff --git a/tests/positionals.rs b/tests/positionals.rs
index 3bf85ee..f808a03 100644
--- a/tests/positionals.rs
+++ b/tests/positionals.rs
@@ -2,6 +2,7 @@ use bpaf::*;
 
 #[test]
 fn positional_with_help() {
+    set_override(false);
     let user = positional::<String>("USER").help("github user\nin two lines");
     let api = positional::<String>("API_KEY").help("api key to use");
     let parser = construct!(user, api).to_options();
@@ -26,6 +27,7 @@ Available options:
 
 #[test]
 fn help_for_positional() {
+    set_override(false);
     let c = positional::<String>("C").help("help for\nc");
     let d = positional::<String>("DDD").help("help for\nddd");
     let parser = construct!(c, d).to_options();
@@ -71,6 +73,7 @@ fn helpful_error_message() {
 
 #[test]
 fn positional_argument() {
+    set_override(false);
     let p = positional::<String>("FILE").group_help("File to process");
     let parser = p.to_options();
 
diff --git a/tests/tests.rs b/tests/tests.rs
index 949e308..61f6622 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -20,6 +20,7 @@ fn construct_with_fn() {
         short('c').switch()
     }
 
+    set_override(false);
     let parser = construct!(Opts { a(), b, c() }).to_options();
     let help = parser
         .run_inner(Args::from(&["--help"]))
@@ -68,6 +69,7 @@ fn simple_two_optional_flags() {
         .unwrap_stderr();
     assert_eq!("-a is not expected in this context", err);
 
+    set_override(false);
     let help = decorated
         .run_inner(Args::from(&["-h"]))
         .unwrap_err()
@@ -138,6 +140,7 @@ fn either_of_three_required_flags() {
         .unwrap_stdout();
     assert_eq!("Version: 1.0\n", ver);
 
+    set_override(false);
     // help is always generated
     let help = decorated
         .run_inner(Args::from(&["-h"]))
@@ -180,6 +183,7 @@ fn either_of_three_required_flags2() {
         .unwrap_stdout();
     assert_eq!("Version: 1.0\n", ver);
 
+    set_override(false);
     // help is always generated
     let help = decorated
         .run_inner(Args::from(&["-h"]))
@@ -222,6 +226,7 @@ fn either_of_two_required_flags_and_one_optional() {
         .unwrap_stdout();
     assert_eq!("Version: 1.0\n", ver);
 
+    set_override(false);
     // help is always generated
     let help = decorated
         .run_inner(Args::from(&["-h"]))
@@ -286,6 +291,7 @@ fn default_arguments() {
     let a = short('a').argument::<i32>("ARG").fallback(42);
     let decorated = a.to_options();
 
+    set_override(false);
     let help = decorated
         .run_inner(Args::from(&["-h"]))
         .unwrap_err()
@@ -342,6 +348,7 @@ fn parse_errors() {
 
 #[test]
 fn custom_usage() {
+    set_override(false);
     let a = short('a').long("long").argument::<String>("ARG");
     let parser = a.to_options().usage("Usage: -a <ARG> or --long <ARG>");
     let help = parser
@@ -360,6 +367,7 @@ Available options:
 
 #[test]
 fn long_usage_string() {
+    set_override(false);
     let a = short('a')
         .long("a-very-long-flag-with")
         .argument::<String>("ARG");
@@ -413,6 +421,7 @@ Available options:
 
 #[test]
 fn group_help_args() {
+    set_override(false);
     let a = short('a').help("flag A, related to B").switch();
     let b = short('b').help("flag B, related to A").switch();
     let c = short('c').help("flag C, unrelated").switch();
@@ -440,6 +449,7 @@ Available options:
 
 #[test]
 fn group_help_commands() {
+    set_override(false);
     let a = short('a')
         .switch()
         .to_options()
@@ -519,6 +529,7 @@ fn from_several_alternatives_pick_more_meaningful() {
 
 #[test]
 fn subcommands() {
+    set_override(false);
     let bar = short('b').switch();
 
     let bar_cmd = command("bar", bar.to_options().descr("This is local info"));
@@ -560,6 +571,7 @@ Available options:
 
 #[test]
 fn multiple_aliases() {
+    set_override(false);
     let a = short('a').short('b').short('c').req_flag(());
     let parser = a.to_options();
 
@@ -646,6 +658,7 @@ mod git {
 
     #[test]
     fn root_help() {
+        set_override(false);
         let parser = setup();
         let expected_help = "\
 The stupid content tracker
@@ -671,6 +684,7 @@ Available commands:
 
     #[test]
     fn fetch_help() {
+        set_override(false);
         let parser = setup();
         let expected_help = "\
 fetches branches from remote repository
@@ -693,6 +707,7 @@ Available options:
 
     #[test]
     fn add_help() {
+        set_override(false);
         let parser = setup();
         let expected_help = "\
 add files to the staging area
@@ -778,6 +793,7 @@ fn arg_bench() {
 
 #[test]
 fn simple_cargo_helper() {
+    set_override(false);
     let a = short('a').long("AAAAA").help("two lines\nof help").switch();
     let b = short('b').switch();
     let parser = construct!(a, b);
@@ -829,6 +845,7 @@ fn long_path_in_construct() {
 
 #[test]
 fn env_variable() {
+    set_override(false);
     let name = "BPAF_SECRET_API_KEY";
     let parser = long("key")
         .env(name)
@@ -876,6 +893,7 @@ Available options:
 
 #[test]
 fn default_plays_nicely_with_command() {
+    set_override(false);
     #[derive(Debug, Clone)]
     enum Foo {
         Foo,
@@ -930,6 +948,7 @@ Available commands:
 
 #[test]
 fn command_with_aliases() {
+    set_override(false);
     let inner = pure(()).to_options().descr("inner descr");
     let cmd = command("foo", inner).long("bar").short('f').short('b');
     let parser = cmd.to_options().descr("outer");
@@ -978,6 +997,7 @@ Available options:
 
 #[test]
 fn help_for_options() {
+    set_override(false);
     let a = short('a').help("help for\na").switch();
     let b = short('c')
         .env("BbBbB")
@@ -1013,6 +1033,7 @@ Available options:
 
 #[test]
 fn help_for_commands() {
+    set_override(false);
     let d = command("thing_d", pure(()).to_options()).help("help for d\ntwo lines");
     let e = command("thing_e", pure(()).to_options())
         .short('e')
@@ -1409,6 +1430,7 @@ fn cargo_show_asm_issue_parse() {
 
 #[test]
 fn cargo_show_asm_issue_unknown_switch() {
+    set_override(false);
     let target = long("flag").switch();
 
     let verbosity = short('v').switch();