Repository: pacak/bpaf
Pull request #202 (2023-04-23 13:46:27)

Use Meta directly to refer to parser components
This should give more consistent `<>` around metavariable names, `[]` around parts that are optional and reduce app size.


Patch:
diff --git a/Changelog.md b/Changelog.md
index 9c777d2..a9c34af 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -19,7 +19,8 @@
 - `bpaf_derive`: top level doc comments on a regular parser are now turned into a `group_help`
 - better error messages for invalid user input
 - env fallback can now be fully hidden
-
+- meta description refactor - invididual parsers should be described more consistently
+  in all sorts of messages
 
 ## bpaf [0.7.10], bpaf_derive [0.3.5] - 2013-03-19
 - improve error messages for typos like `-llvm` instead of `--llvm`
diff --git a/docs/src/adjacent_1/cases.txt b/docs/src/adjacent_1/cases.txt
index da981f6..6c83089 100644
--- a/docs/src/adjacent_1/cases.txt
+++ b/docs/src/adjacent_1/cases.txt
@@ -11,4 +11,4 @@ Options { switch: true, multi: [Rect { item: (), width: 10, height: 10, painted:
 ? But with `adjacent` they cannot interleave
 > --rect --rect --width 10 --painted --height 10 --height 10 --width 10
 Stderr
-Expected --width PX, got "--rect". Pass --help for usage information
+Expected --width <PX>, got "--rect". Pass --help for usage information
diff --git a/docs/src/any/cases.txt b/docs/src/any/cases.txt
index 2a3c116..4047e97 100644
--- a/docs/src/any/cases.txt
+++ b/docs/src/any/cases.txt
@@ -18,7 +18,7 @@ Options { turbo: true, rest: ["git", "commit", "-m=hello world"] }
 ? You can keep `--help` working, but you need to add extra `guard` for that
 > --turbo --help
 Stdout
-Usage: [-t] <REST>...
+Usage: [-t] [<REST>]...
 
 Available positional items:
     <REST>  app will pass anything unused to a child process
diff --git a/docs/src/anywhere/cases.txt b/docs/src/anywhere/cases.txt
index 8a9d481..bdcf80c 100644
--- a/docs/src/anywhere/cases.txt
+++ b/docs/src/anywhere/cases.txt
@@ -22,10 +22,8 @@ Options { turbo: true, backing: true, xinerama: true }
 Stdout
 Usage: [-t] [<backing>] [<xinerama>]
 
-Available positional items:
-    <backing>   Backing status
-    <xinerama>  Xinerama status
-
 Available options:
     -t, --turbo  Engage the turbo mode
+    <backing>    Backing status
+    <xinerama>   Xinerama status
     -h, --help   Prints help information
diff --git a/docs/src/argument/cases.txt b/docs/src/argument/cases.txt
index 4a7e222..3a09934 100644
--- a/docs/src/argument/cases.txt
+++ b/docs/src/argument/cases.txt
@@ -17,7 +17,7 @@ Options { value: 100, shorty: 0 }
 ? But if there's no fallback - the value is required
 > --value 1
 Stderr
-Expected -s ARG, pass --help for usage information
+Expected -s <ARG>, pass --help for usage information
 
 ? Argument is required
 > -s
diff --git a/docs/src/dd/cases.txt b/docs/src/dd/cases.txt
index 96cd926..2722751 100644
--- a/docs/src/dd/cases.txt
+++ b/docs/src/dd/cases.txt
@@ -18,11 +18,9 @@ Options { magic: true, in_file: "-", out_file: "-", block_size: 42 }
 Stdout
 Usage: [--magic] [<if=FILE>] [<of=FILE>] [<bs=SIZE>]
 
-Available positional items:
-    <if=FILE>  read from FILE instead of stdin
-    <of=FILE>  write to FILE instead of stdout
-    <bs=SIZE>  read/write SIZE blocks at once instead of 512
-
 Available options:
         --magic
+    <if=FILE>    read from FILE instead of stdin
+    <of=FILE>    write to FILE instead of stdout
+    <bs=SIZE>    read/write SIZE blocks at once instead of 512
     -h, --help   Prints help information
diff --git a/docs/src/find/cases.txt b/docs/src/find/cases.txt
index 0647b2d..453ccb1 100644
--- a/docs/src/find/cases.txt
+++ b/docs/src/find/cases.txt
@@ -29,11 +29,11 @@ Options { paths: ["src"], exec: Some(["rustc", "{}"]), user: Some("bob"), perm:
 ? bypass it by hiding options and substituting your own with custom `header`/`footer`.
 > --help
 Stdout
-Usage: [<-exec>] [<USER>] [<-mode>] <PATH>...
+Usage: [-exec] [<USER>] [-mode] [<PATH>]...
 
 Available positional items:
-    <-exec>  -exec /path/to/command flags and options ;
-    <-mode>  -mode (perm | -perm | /perm)
+    -exec  -exec /path/to/command flags and options ;
+    -mode  -mode (perm | -perm | /perm)
 
 Available options:
     -h, --help  Prints help information
diff --git a/docs/src/many/cases.txt b/docs/src/many/cases.txt
index 18b0286..2231413 100644
--- a/docs/src/many/cases.txt
+++ b/docs/src/many/cases.txt
@@ -18,7 +18,7 @@ Options { argument: [], switches: [true, true] }
 ? In usage lines `many` items are indicated with `...`
 > --help
 Stdout
-Usage: --argument ARG... [--switch]...
+Usage: [--argument ARG]... [--switch]...
 
 Available options:
         --argument <ARG>  important argument
diff --git a/docs/src/xorg/cases.txt b/docs/src/xorg/cases.txt
index ac17699..f328bde 100644
--- a/docs/src/xorg/cases.txt
+++ b/docs/src/xorg/cases.txt
@@ -13,14 +13,14 @@ Options { turbo: true, backing: false, xinerama: true, extensions: [("banana", t
 ? bypass it by hiding options and substituting your own with custom `header`/`footer`.
 > --help
 Stdout
-Usage: [-t] [<backing>] [<xinerama>] (<+ext> | <-ext>)...
+Usage: [-t] [<backing>] [<xinerama>] [(+ext | -ext) <EXT>]...
 
 Available positional items:
-    <backing>   Backing status
-    <xinerama>  Xinerama status
-    <+ext>      enable ext <EXT>
-    <-ext>      disable ext <EXT>
+    +ext  enable ext <EXT>
+    -ext  disable ext <EXT>
 
 Available options:
     -t, --turbo  Engage the turbo mode
+    <backing>    Backing status
+    <xinerama>   Xinerama status
     -h, --help   Prints help information
diff --git a/examples/xorg.rs b/examples/xorg.rs
index c9dba2e..1217f06 100644
--- a/examples/xorg.rs
+++ b/examples/xorg.rs
@@ -50,7 +50,7 @@ fn extension() -> impl Parser<(String, bool)> {
         });
 
     let state = construct!([on, off]);
-    let name = positional::<String>("EXT").hide();
+    let name = positional::<String>("EXT");
     construct!(state, name)
         .map(|(a, b)| (b, a))
         .anywhere()
diff --git a/src/args.rs b/src/args.rs
index b68cf67..50ab700 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -1,7 +1,7 @@
 use std::ffi::OsString;
 
 pub(crate) use crate::arg::*;
-use crate::{info::Info, parsers::NamedArg, Error, Meta, ParseFailure};
+use crate::{info::Info, meta_help::Metavar, parsers::NamedArg, Error, Meta, ParseFailure};
 
 /// Shows which branch of [`ParseOrElse`] parsed the argument
 #[derive(Debug, Clone)]
@@ -626,7 +626,7 @@ impl Args {
     /// returns Err if first positional argument is a flag
     pub(crate) fn take_positional_word(
         &mut self,
-        metavar: &'static str,
+        metavar: Metavar,
     ) -> Result<Option<(bool, OsString)>, Error> {
         match self.items_iter().next() {
             Some((ix, Arg::PosWord(w))) => {
@@ -642,7 +642,7 @@ impl Args {
                 Ok(Some((false, w)))
             }
             Some((_, arg)) => Err(Error::Message(
-                format!("Expected an argument <{}>, got {}", metavar, arg),
+                format!("Expected an argument {}, got {}", metavar, arg),
                 false,
             )),
             None => Ok(None),
@@ -669,6 +669,7 @@ impl Args {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::meta_help::Metavar;
     use crate::{long, short};
     #[test]
     fn long_arg() {
@@ -683,7 +684,7 @@ mod tests {
         let flag = a.take_flag(&long("speed"));
         assert!(flag);
         assert!(!a.is_empty());
-        let s = a.take_positional_word("SPEED").unwrap().unwrap();
+        let s = a.take_positional_word(Metavar("SPEED")).unwrap().unwrap();
         assert_eq!(s.1, "12");
         assert!(a.is_empty());
     }
@@ -778,7 +779,7 @@ mod tests {
     fn command_and_positional() {
         let mut a = Args::from(&["cmd", "pos"]);
         assert!(a.take_cmd("cmd"));
-        let w = a.take_positional_word("A").unwrap().unwrap();
+        let w = a.take_positional_word(Metavar("A")).unwrap().unwrap();
         assert_eq!(w.1, "pos");
         assert!(a.is_empty());
     }
@@ -787,7 +788,7 @@ mod tests {
     fn positionals_after_double_dash1() {
         let mut a = Args::from(&["-v", "--", "-x"]);
         assert!(a.take_flag(&short('v')));
-        let w = a.take_positional_word("A").unwrap().unwrap();
+        let w = a.take_positional_word(Metavar("A")).unwrap().unwrap();
         assert_eq!(w.1, "-x");
         assert!(a.is_empty());
     }
@@ -796,7 +797,7 @@ mod tests {
     fn positionals_after_double_dash2() {
         let mut a = Args::from(&["-v", "--", "-x"]);
         assert!(a.take_flag(&short('v')));
-        let w = a.take_positional_word("A").unwrap().unwrap();
+        let w = a.take_positional_word(Metavar("A")).unwrap().unwrap();
         assert_eq!(w.1, "-x");
         assert!(a.is_empty());
     }
@@ -806,7 +807,7 @@ mod tests {
         let mut a = Args::from(&["-v", "12", "--", "-x"]);
         let w = a.take_arg(&short('v'), false).unwrap().unwrap();
         assert_eq!(w, "12");
-        let w = a.take_positional_word("A").unwrap().unwrap();
+        let w = a.take_positional_word(Metavar("A")).unwrap().unwrap();
         assert_eq!(w.1, "-x");
         assert!(a.is_empty());
     }
diff --git a/src/docs/adjacent_1.md b/src/docs/adjacent_1.md
index fb1228f..29c21b5 100644
--- a/src/docs/adjacent_1.md
+++ b/src/docs/adjacent_1.md
@@ -60,7 +60,7 @@ Options { switch: true, multi: [Rect { item: (), width: 10, height: 10, painted:
 But with `adjacent` they cannot interleave
 ```console
 % app --rect --rect --width 10 --painted --height 10 --height 10 --width 10
-Expected --width PX, got "--rect". Pass --help for usage information
+Expected --width <PX>, got "--rect". Pass --help for usage information
 ```
 
 </details>
diff --git a/src/docs/any.md b/src/docs/any.md
index ad49ea5..9b006c9 100644
--- a/src/docs/any.md
+++ b/src/docs/any.md
@@ -76,7 +76,7 @@ Options { turbo: true, rest: ["git", "commit", "-m=hello world"] }
 You can keep `--help` working, but you need to add extra `guard` for that
 ```console
 % app --turbo --help
-Usage: [-t] <REST>...
+Usage: [-t] [<REST>]...
 
 Available positional items:
     <REST>  app will pass anything unused to a child process
diff --git a/src/docs/anywhere.md b/src/docs/anywhere.md
index 00b519c..a1f455b 100644
--- a/src/docs/anywhere.md
+++ b/src/docs/anywhere.md
@@ -79,12 +79,10 @@ with `.header` or `.footer` methods on `OptionParser`.
 % app --help
 Usage: [-t] [<backing>] [<xinerama>]
 
-Available positional items:
-    <backing>   Backing status
-    <xinerama>  Xinerama status
-
 Available options:
     -t, --turbo  Engage the turbo mode
+    <backing>    Backing status
+    <xinerama>   Xinerama status
     -h, --help   Prints help information
 ```
 
diff --git a/src/docs/argument.md b/src/docs/argument.md
index 19e6b67..0f9c215 100644
--- a/src/docs/argument.md
+++ b/src/docs/argument.md
@@ -69,7 +69,7 @@ Options { value: 100, shorty: 0 }
 But if there's no fallback - the value is required
 ```console
 % app --value 1
-Expected -s ARG, pass --help for usage information
+Expected -s <ARG>, pass --help for usage information
 ```
 
 Argument is required
diff --git a/src/docs/dd.md b/src/docs/dd.md
index 165a782..f77f06e 100644
--- a/src/docs/dd.md
+++ b/src/docs/dd.md
@@ -91,13 +91,11 @@ Best effort to generate help, but you can always customize it further
 % app --help
 Usage: [--magic] [<if=FILE>] [<of=FILE>] [<bs=SIZE>]
 
-Available positional items:
-    <if=FILE>  read from FILE instead of stdin
-    <of=FILE>  write to FILE instead of stdout
-    <bs=SIZE>  read/write SIZE blocks at once instead of 512
-
 Available options:
         --magic
+    <if=FILE>    read from FILE instead of stdin
+    <of=FILE>    write to FILE instead of stdout
+    <bs=SIZE>    read/write SIZE blocks at once instead of 512
     -h, --help   Prints help information
 ```
 
diff --git a/src/docs/find.md b/src/docs/find.md
index d0f5619..daf4372 100644
--- a/src/docs/find.md
+++ b/src/docs/find.md
@@ -150,11 +150,11 @@ While `bpaf` takes some effort to render the help even for custom stuff - you ca
 bypass it by hiding options and substituting your own with custom `header`/`footer`.
 ```console
 % app --help
-Usage: [<-exec>] [<USER>] [<-mode>] <PATH>...
+Usage: [-exec] [<USER>] [-mode] [<PATH>]...
 
 Available positional items:
-    <-exec>  -exec /path/to/command flags and options ;
-    <-mode>  -mode (perm | -perm | /perm)
+    -exec  -exec /path/to/command flags and options ;
+    -mode  -mode (perm | -perm | /perm)
 
 Available options:
     -h, --help  Prints help information
diff --git a/src/docs/many.md b/src/docs/many.md
index 936204a..d6eed9b 100644
--- a/src/docs/many.md
+++ b/src/docs/many.md
@@ -66,7 +66,7 @@ Options { argument: [], switches: [true, true] }
 In usage lines `many` items are indicated with `...`
 ```console
 % app --help
-Usage: --argument ARG... [--switch]...
+Usage: [--argument ARG]... [--switch]...
 
 Available options:
         --argument <ARG>  important argument
diff --git a/src/docs/xorg.md b/src/docs/xorg.md
index 5350aed..c72ef7e 100644
--- a/src/docs/xorg.md
+++ b/src/docs/xorg.md
@@ -51,7 +51,7 @@ fn extension() -> impl Parser<(String, bool)> {
         });
 
     let state = construct!([on, off]);
-    let name = positional::<String>("EXT").hide();
+    let name = positional::<String>("EXT");
     construct!(state, name)
         .map(|(a, b)| (b, a))
         .anywhere()
@@ -101,16 +101,16 @@ While `bpaf` takes some effort to render the help even for custom stuff - you ca
 bypass it by hiding options and substituting your own with custom `header`/`footer`.
 ```console
 % app --help
-Usage: [-t] [<backing>] [<xinerama>] (<+ext> | <-ext>)...
+Usage: [-t] [<backing>] [<xinerama>] [(+ext | -ext) <EXT>]...
 
 Available positional items:
-    <backing>   Backing status
-    <xinerama>  Xinerama status
-    <+ext>      enable ext <EXT>
-    <-ext>      disable ext <EXT>
+    +ext  enable ext <EXT>
+    -ext  disable ext <EXT>
 
 Available options:
     -t, --turbo  Engage the turbo mode
+    <backing>    Backing status
+    <xinerama>   Xinerama status
     -h, --help   Prints help information
 ```
 
diff --git a/src/help.rs b/src/help.rs
index 6df6718..926dc88 100644
--- a/src/help.rs
+++ b/src/help.rs
@@ -109,21 +109,20 @@ pub(crate) fn improve_error(
         Some(Error::ParseFailure(f)) => return f,
         Some(Error::Message(msg, _)) => msg,
         Some(Error::Missing(xs)) => {
+            let meta = Meta::Or(xs.iter().map(|i| Meta::from(i.clone())).collect::<Vec<_>>())
+                .normalized(false);
+
             if let Some(x) = args.peek() {
                 if let Some(msg) = crate::meta_youmean::suggest(args, inner) {
                     msg
                 } else {
                     format!(
                         "Expected {}, got \"{}\". Pass --help for usage information",
-                        Meta::Or(xs.iter().map(|i| Meta::from(i.clone())).collect::<Vec<_>>()),
-                        x
+                        meta, x
                     )
                 }
             } else {
-                format!(
-                    "Expected {}, pass --help for usage information",
-                    Meta::Or(xs.iter().map(|i| Meta::from(i.clone())).collect::<Vec<_>>())
-                )
+                format!("Expected {}, pass --help for usage information", meta)
             }
         }
     })
diff --git a/src/info.rs b/src/info.rs
index 09bf461..6293f47 100644
--- a/src/info.rs
+++ b/src/info.rs
@@ -4,7 +4,7 @@ use crate::{
     args::{Args, Conflict},
     item::Item,
     parsers::ParseCommand,
-    Meta, ParseFailure, Parser,
+    ParseFailure, Parser,
 };
 
 /// Unsuccessful command line parsing outcome, internal representation
@@ -570,35 +570,6 @@ impl<T> OptionParser<T> {
     ///
     /// `check_invariants` indicates problems with panic
     pub fn check_invariants(&self, _cosmetic: bool) {
-        perform_invariant_check(&self.inner.meta(), true);
-    }
-}
-
-/// do a nested invariant check
-
-/// the check itself is performed as part of `to_usage_meta` transformation `fresh` parameter
-/// is used to perform it only once for every command parser encountered
-fn perform_invariant_check(meta: &Meta, fresh: bool) {
-    if fresh {
-        println!("Checking\n{:#?}", meta);
-        meta.to_usage_meta();
-    }
-    match meta {
-        Meta::And(xs) | Meta::Or(xs) => {
-            for i in xs.iter() {
-                perform_invariant_check(i, false);
-            }
-        }
-        Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) | Meta::Decorated(x, _, _) => {
-            perform_invariant_check(x, false);
-        }
-        Meta::Item(i) => match &**i {
-            Item::Command { meta, .. } => perform_invariant_check(meta, true),
-            Item::Positional { .. }
-            | Item::Flag { .. }
-            | Item::Argument { .. }
-            | Item::MultiArg { .. } => {}
-        },
-        Meta::Skip => {}
+        self.inner.meta().positional_invariant_check(true);
     }
 }
diff --git a/src/item.rs b/src/item.rs
index 4508a41..6e5f274 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -1,10 +1,10 @@
-use crate::{info::Info, parsers::NamedArg, Meta};
+use crate::{info::Info, meta_help::Metavar, parsers::NamedArg, Meta};
 
 #[doc(hidden)]
 #[derive(Clone, Debug)]
 pub enum Item {
     Positional {
-        metavar: &'static str,
+        metavar: Metavar,
         strict: bool,
         help: Option<String>,
     },
@@ -17,25 +17,37 @@ pub enum Item {
     },
     Flag {
         name: ShortLong,
+        /// used for disambiguation
         shorts: Vec<char>,
         env: Option<&'static str>,
         help: Option<String>,
     },
     Argument {
         name: ShortLong,
+        /// used for disambiguation
         shorts: Vec<char>,
-        metavar: &'static str,
+        metavar: Metavar,
         env: Option<&'static str>,
         help: Option<String>,
     },
     MultiArg {
         name: ShortLong,
+        /// used for disambiguation
         shorts: Vec<char>,
         help: Option<String>,
-        fields: Vec<(&'static str, Option<String>)>,
+        fields: Vec<(Metavar, Option<String>)>,
     },
 }
 
+impl Item {
+    pub(crate) fn is_pos(&self) -> bool {
+        match self {
+            Item::Positional { .. } | Item::Command { .. } => true,
+            Item::Flag { .. } | Item::Argument { .. } | Item::MultiArg { .. } => false,
+        }
+    }
+}
+
 #[doc(hidden)]
 #[derive(Copy, Clone, Debug)]
 pub enum ShortLong {
@@ -66,3 +78,64 @@ impl Item {
         }
     }
 }
+
+impl std::fmt::Display for ShortLong {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ShortLong::Long(l) => write!(f, "--{}", l),
+            ShortLong::Short(s) | ShortLong::ShortLong(s, _) => write!(f, "-{}", s),
+        }
+    }
+}
+
+impl std::fmt::Display for Item {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Item::Positional {
+                metavar,
+                strict,
+                help: _,
+            } => {
+                if *strict {
+                    f.write_str("-- ")?;
+                }
+                // use write! macro to reset the alternative view
+                write!(f, "{}", metavar)
+            }
+            Item::Command { .. } => f.write_str("COMMAND ..."),
+            Item::Flag {
+                name,
+                shorts: _,
+                env: _,
+                help: _,
+            } => write!(f, "{}", name),
+
+            Item::Argument {
+                name,
+                shorts: _,
+                metavar,
+                env: _,
+                help: _,
+            } => {
+                name.fmt(f)?;
+                f.write_str(" ")?;
+                metavar.fmt(f)
+            }
+
+            Item::MultiArg {
+                name,
+                shorts: _,
+                help: _,
+                fields,
+            } => {
+                name.fmt(f)?;
+
+                for (metavar, _) in fields {
+                    f.write_str(" ")?;
+                    metavar.fmt(f)?;
+                }
+                Ok(())
+            }
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index aec1297..3eaa87b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -404,7 +404,6 @@ mod item;
 mod manpage;
 mod meta;
 mod meta_help;
-mod meta_usage;
 mod meta_youmean;
 pub mod params;
 mod structs;
diff --git a/src/manpage.rs b/src/manpage.rs
index 28e7b12..0df5762 100644
--- a/src/manpage.rs
+++ b/src/manpage.rs
@@ -1,9 +1,9 @@
 use roff::{Inline, Roff};
 
 use crate::{
-    item::ShortLong,
+    item::{Item, ShortLong},
     meta_help::{HelpItem, HelpItems},
-    OptionParser,
+    Meta, OptionParser,
 };
 
 struct Manpage {
@@ -152,14 +152,16 @@ impl Line<'_> {
         self
     }
 
-    fn shortlong(&mut self, name: ShortLong) -> &mut Self {
+    fn shortlong(&mut self, name: ShortLong, long: bool) -> &mut Self {
         match name {
             ShortLong::Short(s) => self.line.push(bold(format!("-{}", s))),
             ShortLong::Long(l) => self.line.push(bold(format!("--{}", l))),
             ShortLong::ShortLong(s, l) => {
                 self.line.push(bold(format!("-{}", s)));
-                self.line.push(norm(", "));
-                self.line.push(bold(format!("--{}", l)));
+                if long {
+                    self.line.push(norm(", "));
+                    self.line.push(bold(format!("--{}", l)));
+                }
             }
         }
         self
@@ -186,10 +188,55 @@ impl Line<'_> {
         self.norm(" ")
     }
 
-    fn usage(&mut self, usage: &crate::meta_usage::UsageMeta) -> &mut Self {
-        use crate::meta_usage::UsageMeta;
+    fn usage_item(&mut self, item: &Item) -> &mut Self {
+        match item {
+            Item::Positional {
+                metavar,
+                strict: _,
+                help: _,
+            } => {
+                self.metavar(metavar.0);
+            }
+            Item::Command { .. } => {
+                self.bold("COMMAND ...");
+            }
+            Item::Flag {
+                name,
+                shorts: _,
+                env: _,
+                help: _,
+            } => {
+                self.shortlong(*name, false);
+            }
+            Item::Argument {
+                name,
+                shorts: _,
+                metavar,
+                env: _,
+                help: _,
+            } => {
+                self.shortlong(*name, false).norm('=').metavar(metavar.0);
+            }
+            Item::MultiArg {
+                name,
+                shorts: _,
+                help: _,
+                fields,
+            } => {
+                self.shortlong(*name, false);
+                for (ix, (mv, _)) in fields.iter().enumerate() {
+                    if ix > 0 {
+                        self.norm(' ');
+                    }
+                    self.italic(mv.0);
+                }
+            }
+        }
+        self
+    }
+    fn usage(&mut self, usage: &Meta) -> &mut Self {
         match usage {
-            UsageMeta::And(xs) => {
+            Meta::And(xs) => {
                 for (ix, x) in xs.iter().enumerate() {
                     if ix > 0 {
                         self.norm(" ");
@@ -197,7 +244,7 @@ impl Line<'_> {
                     self.usage(x);
                 }
             }
-            UsageMeta::Or(xs) => {
+            Meta::Or(xs) => {
                 for (ix, x) in xs.iter().enumerate() {
                     if ix > 0 {
                         self.norm(" | ");
@@ -205,32 +252,27 @@ impl Line<'_> {
                     self.usage(x);
                 }
             }
-            UsageMeta::Required(u) => {
+            Meta::Required(u) => {
                 self.norm('(').usage(u).norm(')');
             }
-            UsageMeta::Optional(meta) => {
-                self.norm('[').usage(meta).norm(']');
-            }
-            UsageMeta::Many(usage) => {
-                self.usage(usage).norm("...");
-            }
-            UsageMeta::ShortFlag(name) => {
-                self.norm('-').bold(*name);
+            Meta::Optional(m) => {
+                self.norm('[').usage(m).norm(']');
             }
-            UsageMeta::ShortArg(name, metavar) => {
-                self.norm('-').bold(*name).norm('=').italic(metavar);
+            Meta::Many(m) => {
+                self.usage(m).norm("...");
             }
-            UsageMeta::LongFlag(name) => {
-                self.norm("--").bold(*name);
+            Meta::Anywhere(m) => {
+                self.usage(m);
             }
-            UsageMeta::LongArg(name, metavar) => {
-                self.norm("--").bold(*name).norm('=').italic(metavar);
+            Meta::Item(item) => {
+                self.usage_item(item);
             }
-            UsageMeta::Pos(x) | UsageMeta::StrictPos(x) => {
-                self.metavar(x);
+            Meta::Decorated(m, _, _) => {
+                self.usage(m);
             }
-            UsageMeta::Command => {
-                self.bold("COMMAND").norm("...");
+            Meta::Skip => {}
+            Meta::HideUsage(m) => {
+                self.usage(m);
             }
         };
         self
@@ -342,7 +384,7 @@ fn help_item(manpage: &mut Manpage, item: HelpItem, command_path: Option<&str>)
         }
         HelpItem::Flag { name, help, env: _ } => {
             manpage.label(|l| {
-                l.shortlong(name);
+                l.shortlong(name, true);
             });
             if let Some(help) = help {
                 manpage.text([norm(help)]);
@@ -355,7 +397,7 @@ fn help_item(manpage: &mut Manpage, item: HelpItem, command_path: Option<&str>)
             help,
         } => {
             manpage.label(|l| {
-                l.shortlong(name).norm("=").metavar(mvar.0);
+                l.shortlong(name, true).norm("=").metavar(mvar.0);
                 if let Some(env) = env {
                     l.env(env);
                 }
@@ -367,12 +409,12 @@ fn help_item(manpage: &mut Manpage, item: HelpItem, command_path: Option<&str>)
         }
         HelpItem::MultiArg { name, help, fields } => {
             manpage.label(|l| {
-                l.shortlong(name).norm("=");
+                l.shortlong(name, true).norm("=");
                 for (ix, (m, _help)) in fields.iter().enumerate() {
                     if ix > 0 {
                         l.norm(" ");
                     }
-                    l.metavar(m);
+                    l.metavar(m.0);
                 }
             });
 
@@ -449,11 +491,11 @@ impl<T> OptionParser<T> {
         });
 
         manpage.section("SYNOPSIS");
-        match meta.to_usage_meta() {
-            Some(usage) => manpage.paragraph(|l| {
-                l.bold(app).space().usage(&usage);
+        match meta.normalized(false) {
+            Meta::Skip => manpage.text([bold(app), norm(" takes no parameters")]),
+            meta => manpage.paragraph(|l| {
+                l.bold(app).space().usage(&meta);
             }),
-            None => manpage.text([bold(app), norm(" takes no parameters")]),
         };
 
         manpage.section("DESCRIPTION");
diff --git a/src/meta.rs b/src/meta.rs
index ac9a42d..7590e5d 100644
--- a/src/meta.rs
+++ b/src/meta.rs
@@ -12,6 +12,8 @@ pub enum Meta {
     And(Vec<Meta>),
     Or(Vec<Meta>),
     Optional(Box<Meta>),
+    Required(Box<Meta>),
+    Anywhere(Box<Meta>),
     Item(Box<Item>),
     Many(Box<Meta>),
     Decorated(Box<Meta>, String, DecorPlace),
@@ -19,11 +21,236 @@ pub enum Meta {
     HideUsage(Box<Meta>),
 }
 
+impl Default for Meta {
+    fn default() -> Self {
+        Meta::Skip
+    }
+}
+
 impl std::fmt::Display for Meta {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self.to_usage_meta() {
-            Some(usage) => usage.fmt(f),
-            None => f.write_str("no parameters expected"),
+        fn go(meta: &Meta, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            match meta {
+                Meta::And(xs) => {
+                    for (ix, x) in xs.iter().enumerate() {
+                        if ix != 0 {
+                            f.write_str(" ")?;
+                        }
+                        go(x, f)?;
+                    }
+                    Ok(())
+                }
+                Meta::Or(xs) => {
+                    for (ix, x) in xs.iter().enumerate() {
+                        if ix != 0 {
+                            f.write_str(" | ")?;
+                        }
+                        go(x, f)?;
+                    }
+                    Ok(())
+                }
+                Meta::Optional(m) => {
+                    f.write_str("[")?;
+                    go(m, f)?;
+                    f.write_str("]")
+                }
+                Meta::Required(m) => {
+                    f.write_str("(")?;
+                    go(m, f)?;
+                    f.write_str(")")
+                }
+                Meta::Item(i) => i.fmt(f),
+                Meta::Many(m) => {
+                    go(m, f)?;
+                    f.write_str("...")
+                }
+
+                // hmm... Do I want to use special syntax here?
+                Meta::Anywhere(m) => m.fmt(f),
+                Meta::Decorated(m, _, _) => m.fmt(f),
+                Meta::Skip => f.write_str("no parameters expected"),
+                Meta::HideUsage(m) => {
+                    if !f.alternate() {
+                        go(m, f)?;
+                    }
+                    Ok(())
+                }
+            }
+        }
+
+        let meta = self.normalized(f.alternate());
+        go(&meta, f)
+    }
+}
+
+impl Meta {
+    fn is_command(&self) -> bool {
+        if let Meta::Item(item) = self {
+            if let Item::Command { .. } = item.as_ref() {
+                return true;
+            }
+        } else if let Meta::Decorated(m, _, _) = self {
+            return m.is_command();
+        }
+
+        false
+    }
+
+    /// do a nested invariant check
+    pub(crate) fn positional_invariant_check(&self, verbose: bool) {
+        fn go(meta: &Meta, is_pos: &mut bool, v: bool) {
+            match meta {
+                Meta::And(xs) => {
+                    for x in xs {
+                        go(x, is_pos, v);
+                    }
+                }
+                Meta::Or(xs) => {
+                    let mut out = *is_pos;
+                    for x in xs {
+                        let mut this_pos = *is_pos;
+                        go(x, &mut this_pos, v);
+                        out |= this_pos;
+                    }
+                    *is_pos = out;
+                }
+                Meta::Item(i) => {
+                    match (*is_pos, i.is_pos()) {
+                        (true, true) | (false, false) => {}
+                        (true, false) => {
+                            panic!("bpaf usage BUG: all positional and command items must be placed in the right \
+                        most position of the structure or tuple they are in but {:?} breaks this rule. \
+                        See bpaf documentation for `positional` for details.", i);
+                        }
+                        (false, true) => {
+                            *is_pos = true;
+                        }
+                    }
+                    if let Item::Command { meta, .. } = &**i {
+                        let mut command_pos = false;
+                        if v {
+                            println!("Checking\n{:#?}", meta);
+                        }
+                        go(meta, &mut command_pos, v);
+                    }
+                }
+                Meta::Anywhere(m) => {
+                    let mut inner = false;
+                    go(m, &mut inner, v);
+                }
+                Meta::Optional(m)
+                | Meta::Required(m)
+                | Meta::Many(m)
+                | Meta::HideUsage(m)
+                | Meta::Decorated(m, _, _) => go(m, is_pos, v),
+                Meta::Skip => {}
+            }
+        }
+        let mut is_pos = false;
+        if verbose {
+            println!("Checking\n{:#?}", self);
+        }
+        go(self, &mut is_pos, verbose);
+    }
+
+    pub(crate) fn normalized(&self, for_usage: bool) -> Meta {
+        let mut m = self.clone();
+        m.normalize(for_usage);
+        // stip outer () around meta unless inner
+        if let Meta::Required(i) = m {
+            m = *i;
+        }
+        if matches!(m, Meta::Or(_)) {
+            m = Meta::Required(Box::new(m));
+        }
+        m
+    }
+
+    /// Normalize meta info for display as usage. Required propagates outwards
+    fn normalize(&mut self, for_usage: bool) {
+        fn normalize_vec(xs: &mut Vec<Meta>, for_usage: bool) -> Option<Meta> {
+            xs.iter_mut().for_each(|m| m.normalize(for_usage));
+            xs.retain(|m| !matches!(m, Meta::Skip));
+            match xs.len() {
+                0 => Some(Meta::Skip),
+                1 => Some(xs.remove(0)),
+                _ => None,
+            }
+        }
+
+        match self {
+            Meta::And(xs) => {
+                if let Some(replacement) = normalize_vec(xs, for_usage) {
+                    *self = replacement;
+                }
+            }
+            // or should have either () or [] around it
+            Meta::Or(xs) => {
+                if let Some(replacement) = normalize_vec(xs, for_usage) {
+                    *self = replacement;
+                } else {
+                    let mut saw_cmd = false;
+                    // drop all the commands apart from the first one
+                    xs.retain(|m| {
+                        let is_cmd = m.is_command();
+                        let keep = !(is_cmd && saw_cmd);
+                        saw_cmd |= is_cmd;
+                        keep
+                    });
+                    match xs.len() {
+                        0 => *self = Meta::Skip,
+                        1 => *self = xs.remove(0),
+                        _ => *self = Meta::Required(Box::new(std::mem::take(self))),
+                    }
+                }
+            }
+            Meta::Optional(m) => {
+                m.normalize(for_usage);
+                if matches!(**m, Meta::Skip) {
+                    *self = Meta::Skip;
+                } else if let Meta::Required(mm) | Meta::Optional(mm) = m.as_mut() {
+                    // Optional(Required(m)) => Optional(m)
+                    // Optional(Optional(m)) => Optional(m)
+                    *m = std::mem::take(mm);
+                }
+            }
+            Meta::Required(m) => {
+                m.normalize(for_usage);
+                if matches!(**m, Meta::Skip) {
+                    *self = Meta::Skip;
+                } else if matches!(**m, Meta::And(_) | Meta::Or(_)) {
+                    // keep () around composite parsers
+                } else {
+                    // Required(Required(m)) => Required(m)
+                    // Required(Optional(m)) => Optional(m)
+                    *self = std::mem::take(m);
+                }
+            }
+            Meta::Many(m) => {
+                m.normalize(for_usage);
+                if matches!(**m, Meta::Skip) {
+                    *self = Meta::Skip;
+                }
+            }
+            Meta::Decorated(m, _, _) => {
+                m.normalize(for_usage);
+                *self = std::mem::take(m);
+            }
+            Meta::Anywhere(m) => {
+                m.normalize(for_usage);
+                if matches!(**m, Meta::Skip) {
+                    *self = Meta::Skip;
+                }
+            }
+            Meta::Item(_) | Meta::Skip => {
+                // nothing to do with items and skip just bubbles upwards
+            }
+            Meta::HideUsage(m) => {
+                m.normalize(for_usage);
+                if for_usage || matches!(**m, Meta::Skip) {
+                    *self = Meta::Skip;
+                }
+            }
         }
     }
 }
@@ -69,7 +296,12 @@ impl Meta {
                     args.extend(shorts);
                 }
             },
-            Meta::HideUsage(m) | Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _, _) => {
+            Meta::HideUsage(m)
+            | Meta::Required(m)
+            | Meta::Optional(m)
+            | Meta::Anywhere(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 1c05733..2b5025e 100644
--- a/src/meta_help.rs
+++ b/src/meta_help.rs
@@ -12,14 +12,30 @@ use crate::{
 
 #[doc(hidden)]
 #[derive(Debug, Clone, Copy)]
-pub(crate) struct Metavar(pub(crate) &'static str);
+pub struct Metavar(pub(crate) &'static str);
+
+impl Metavar {
+    // don't render <> around the metavar if
+    fn custom(&self) -> bool {
+        self.0
+            .as_bytes()
+            .first()
+            .map_or(false, |c| !c.is_ascii_alphanumeric())
+    }
+}
 
 impl std::fmt::Display for Metavar {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         use std::fmt::Write;
-        f.write_char('<')?;
+        let hide_triangles = f.alternate() || self.custom();
+        if !hide_triangles {
+            f.write_char('<')?;
+        }
         f.write_str(self.0)?;
-        f.write_char('>')
+        if !hide_triangles {
+            f.write_char('>')?;
+        }
+        Ok(())
     }
 }
 
@@ -57,7 +73,7 @@ pub(crate) enum HelpItem<'a> {
     MultiArg {
         name: ShortLong,
         help: Option<&'a str>,
-        fields: &'a [(&'static str, Option<String>)],
+        fields: &'a [(Metavar, Option<String>)],
     },
 }
 
@@ -69,6 +85,7 @@ pub(crate) struct HelpItems<'a> {
     pub(crate) flgs: Vec<HelpItem<'a>>,
     pub(crate) psns: Vec<HelpItem<'a>>,
     pub(crate) cmds: Vec<HelpItem<'a>>,
+    anywhere: bool,
 }
 
 impl HelpItem<'_> {
@@ -98,10 +115,15 @@ impl<'a> HelpItems<'a> {
                 strict: _,
             } => {
                 if help.is_some() {
-                    self.psns.push(HelpItem::Positional {
-                        metavar: Metavar(metavar),
+                    let hi = HelpItem::Positional {
+                        metavar: *metavar,
                         help: help.as_deref(),
-                    });
+                    };
+                    if self.anywhere {
+                        self.flgs.push(hi);
+                    } else {
+                        self.psns.push(hi);
+                    }
                 }
             }
             Item::Command {
@@ -118,7 +140,7 @@ impl<'a> HelpItems<'a> {
                 #[cfg(feature = "manpage")]
                 meta,
             } => {
-                self.cmds.push(HelpItem::Command {
+                let hi = HelpItem::Command {
                     name,
                     #[cfg(feature = "manpage")]
                     info,
@@ -126,7 +148,12 @@ impl<'a> HelpItems<'a> {
                     meta,
                     short: *short,
                     help: help.as_deref(),
-                });
+                };
+                if self.anywhere {
+                    self.flgs.push(hi);
+                } else {
+                    self.cmds.push(hi);
+                }
             }
             Item::Flag {
                 name,
@@ -146,7 +173,7 @@ impl<'a> HelpItems<'a> {
                 shorts: _,
             } => self.flgs.push(HelpItem::Argument {
                 name: *name,
-                metavar: Metavar(metavar),
+                metavar: *metavar,
                 env: *env,
                 help: help.as_deref(),
             }),
@@ -171,7 +198,15 @@ impl<'a> HelpItems<'a> {
                     self.classify(x);
                 }
             }
-            Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) => self.classify(x),
+            Meta::Anywhere(x) => {
+                let prev_anywhere = self.anywhere;
+                self.anywhere = true;
+                self.classify(x);
+                self.anywhere = prev_anywhere;
+            }
+            Meta::HideUsage(x) | Meta::Required(x) | Meta::Optional(x) | Meta::Many(x) => {
+                self.classify(x)
+            }
             Meta::Item(item) => self.classify_item(item),
 
             Meta::Decorated(m, help, DecorPlace::Header) => {
@@ -256,7 +291,7 @@ impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
                 help,
                 strict: _,
             } => Self::Positional {
-                metavar: Metavar(metavar),
+                metavar: *metavar,
                 help: help.as_deref(),
             },
             Item::Command {
@@ -298,7 +333,7 @@ impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
                 shorts: _,
             } => Self::Argument {
                 name: *name,
-                metavar: Metavar(metavar),
+                metavar: *metavar,
                 env: *env,
                 help: help.as_deref(),
             },
@@ -317,9 +352,14 @@ impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
 }
 
 fn write_metavar(buf: &mut Buffer, metavar: Metavar) {
-    buf.write_char('<', Style::Label);
+    let custom = metavar.custom();
+    if !custom {
+        buf.write_char('<', Style::Label);
+    }
     buf.write_str(metavar.0, Style::Label);
-    buf.write_char('>', Style::Label);
+    if !custom {
+        buf.write_char('>', Style::Label);
+    }
 }
 
 fn write_help_item(buf: &mut Buffer, item: &HelpItem) {
@@ -416,7 +456,7 @@ fn write_help_item(buf: &mut Buffer, item: &HelpItem) {
             write_shortlong(buf, *name);
             for (field, _) in fields.iter() {
                 buf.write_str(" ", Style::Label);
-                write_metavar(buf, Metavar(field));
+                write_metavar(buf, *field);
             }
 
             if let Some(help) = help {
@@ -427,7 +467,7 @@ fn write_help_item(buf: &mut Buffer, item: &HelpItem) {
             for (field, help) in fields.iter() {
                 if let Some(help) = help {
                     buf.newline();
-                    write_metavar(buf, Metavar(field));
+                    write_metavar(buf, *field);
                     buf.tabstop();
                     buf.write_str(help, Style::Text);
                 }
@@ -487,26 +527,21 @@ pub fn render_help(info: &Info, parser_meta: &Meta, help_meta: &Meta) -> String
     let mut res = String::new();
     let mut buf = Buffer::default();
 
+    parser_meta.positional_invariant_check(false);
+
     if let Some(t) = info.descr {
         write_as_lines(&mut buf, t);
         buf.newline();
     }
-
-    let auto = parser_meta.to_usage_meta().map(|u| u.to_string());
+    let auto = format!("{:#}", parser_meta);
     if let Some(custom_usage) = info.usage {
-        match auto {
-            Some(auto_usage) => buf.write_str(
-                custom_usage.replacen("{usage}", &auto_usage, 1).as_str(),
-                Style::Text,
-            ),
-            None => buf.write_str(custom_usage, Style::Text),
-        };
-        buf.newline();
-    } else if let Some(usage) = auto {
-        buf.write_str("Usage: ", Style::Text);
+        let usage = custom_usage.replacen("{usage}", &auto, 1);
         buf.write_str(&usage, Style::Text);
-        buf.newline();
+    } else {
+        buf.write_str("Usage: ", Style::Text);
+        buf.write_str(&auto, Style::Text);
     }
+    buf.newline();
 
     if let Some(t) = info.header {
         buf.newline();
diff --git a/src/meta_usage.rs b/src/meta_usage.rs
deleted file mode 100644
index f3f16c7..0000000
--- a/src/meta_usage.rs
+++ /dev/null
@@ -1,207 +0,0 @@
-use crate::{
-    item::{Item, ShortLong},
-    Meta,
-};
-
-#[derive(Debug)]
-pub(crate) enum UsageMeta {
-    And(Vec<Self>),
-    Or(Vec<Self>),
-    Required(Box<Self>),
-    Optional(Box<Self>),
-    Many(Box<Self>),
-    ShortFlag(char),
-    ShortArg(char, String),
-    LongFlag(&'static str),
-    LongArg(&'static str, String),
-    Pos(&'static str),
-    StrictPos(&'static str),
-    Command,
-}
-
-// positional validity rules:
-// - any meta containing a positional becomes positional itself
-// - positional item must occupy the right most position inside Meta::And
-
-impl Meta {
-    /// Represent [`Meta`] as [`UsageMeta`]
-    ///
-    /// `None` indicates no parameters - usage line isn't shown
-    pub(crate) fn to_usage_meta(&self) -> Option<UsageMeta> {
-        let mut is_pos = false;
-
-        let usage = collect_usage_meta(self, &mut is_pos)?;
-        if let UsageMeta::Or(..) = &usage {
-            Some(UsageMeta::Required(Box::new(usage)))
-        } else {
-            Some(usage)
-        }
-    }
-}
-/// Transforms `Meta` to [`UsageMeta`]
-///
-/// return value is None if parser takes no parameters at all
-///
-/// 1. OR construct with 1 item is just one item
-/// 2. AND construct with 1 item is just one item
-/// 3. Flatten Commands inside OR constructs
-/// 4  Set required for top level
-/// 5. Set required for Items inside OR and AND constructs
-/// 6. Set optional for Items marked inside [`Meta::Optional`]
-/// 7. put optional OR construct into [`UM::Optional`]
-/// 8. put required OR constructs into [`UM::Required`]
-/// 9. put [] around any optional item
-/// 10. put () around AND construct and commands inside [`Meta::Many`]
-fn collect_usage_meta(meta: &Meta, is_pos: &mut bool) -> Option<UsageMeta> {
-    let r = match meta {
-        Meta::And(xs) => {
-            let mut items = xs
-                .iter()
-                .filter_map(|x| {
-                    let mut this_pos = false;
-                    let usage_meta = collect_usage_meta(x, &mut this_pos)?;
-                    assert!(!*is_pos || this_pos,
-                        "bpaf usage BUG: all positional and command items must be placed in the right \
-                        most position of the structure or tuple they are in but {} breaks this rule. \
-                        See bpaf documentation for `positional` for details.",
-                        usage_meta
-                    );
-                    *is_pos |= this_pos;
-
-                    if let UsageMeta::Or(_) = &usage_meta {
-                        Some(UsageMeta::Required(Box::new(usage_meta)))
-                    } else {
-                    Some(usage_meta)
-                    }
-                })
-                .collect::<Vec<_>>();
-            match items.len() {
-                0 => return None,
-                1 => items.remove(0),
-                _ => UsageMeta::And(items),
-            }
-        }
-
-        Meta::Or(xs) => {
-            let mut saw_command = false;
-            let mut any_pos = false;
-            let mut items = xs
-                .iter()
-                .filter_map(|x| {
-                    let mut top_pos = *is_pos;
-                    let usage_meta = collect_usage_meta(x, &mut top_pos)?;
-                    any_pos |= top_pos;
-                    #[allow(clippy::equatable_if_let)]
-                    if let UsageMeta::Command = &usage_meta {
-                        if saw_command {
-                            None
-                        } else {
-                            saw_command = true;
-                            Some(usage_meta)
-                        }
-                    } else {
-                        Some(usage_meta)
-                    }
-                })
-                .collect::<Vec<_>>();
-            *is_pos |= any_pos;
-            match items.len() {
-                0 => return None,
-                1 => items.remove(0),
-                _ => UsageMeta::Or(items),
-            }
-        }
-        Meta::Optional(m) => {
-            let inner = collect_usage_meta(m, is_pos)?;
-            UsageMeta::Optional(Box::new(inner))
-        }
-        Meta::Many(meta) => {
-            let inner = collect_usage_meta(meta, is_pos)?;
-            if let UsageMeta::And(..) | UsageMeta::Or(..) = &inner {
-                UsageMeta::Many(Box::new(UsageMeta::Required(Box::new(inner))))
-            } else {
-                UsageMeta::Many(Box::new(inner))
-            }
-        }
-        Meta::Decorated(meta, _, _) => collect_usage_meta(meta, is_pos)?,
-        Meta::HideUsage(_) | Meta::Skip => return None,
-        Meta::Item(i) => match &**i {
-            Item::Positional {
-                metavar, strict, ..
-            } => {
-                *is_pos = true;
-                if *strict {
-                    UsageMeta::StrictPos(metavar)
-                } else {
-                    UsageMeta::Pos(metavar)
-                }
-            }
-            Item::Command { .. } => {
-                *is_pos = true;
-                UsageMeta::Command
-            }
-            Item::Flag { name, .. } => match name {
-                ShortLong::Short(s) | ShortLong::ShortLong(s, _) => UsageMeta::ShortFlag(*s),
-                ShortLong::Long(l) => UsageMeta::LongFlag(l),
-            },
-            Item::Argument { name, metavar, .. } => match name {
-                ShortLong::Short(s) | ShortLong::ShortLong(s, _) => {
-                    UsageMeta::ShortArg(*s, (*metavar).to_string())
-                }
-                ShortLong::Long(l) => UsageMeta::LongArg(l, (*metavar).to_string()),
-            },
-            Item::MultiArg { name, fields, .. } => {
-                let mut args = String::new();
-                for (var, _) in fields.iter() {
-                    if !args.is_empty() {
-                        args.push(' ');
-                    }
-                    args.push_str(var);
-                }
-                match name {
-                    ShortLong::Short(s) | ShortLong::ShortLong(s, _) => {
-                        UsageMeta::ShortArg(*s, args)
-                    }
-                    ShortLong::Long(l) => UsageMeta::LongArg(l, args),
-                }
-            }
-        },
-    };
-    Some(r)
-}
-
-impl std::fmt::Display for UsageMeta {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        use std::fmt::Write;
-        match self {
-            UsageMeta::And(xs) => {
-                for (ix, x) in xs.iter().enumerate() {
-                    if ix != 0 {
-                        f.write_char(' ')?;
-                    }
-                    x.fmt(f)?;
-                }
-                Ok(())
-            }
-            UsageMeta::Or(xs) => {
-                for (ix, x) in xs.iter().enumerate() {
-                    if ix != 0 {
-                        f.write_str(" | ")?;
-                    }
-                    x.fmt(f)?;
-                }
-                Ok(())
-            }
-            UsageMeta::Required(x) => write!(f, "({})", x),
-            UsageMeta::Optional(x) => write!(f, "[{}]", x),
-            UsageMeta::Many(x) => write!(f, "{}...", x),
-            UsageMeta::ShortFlag(c) => write!(f, "-{}", c),
-            UsageMeta::LongFlag(l) => write!(f, "--{}", l),
-            UsageMeta::ShortArg(c, v) => write!(f, "-{} {}", c, v),
-            UsageMeta::LongArg(l, v) => write!(f, "--{} {}", l, v),
-            UsageMeta::Command => f.write_str("COMMAND ..."),
-            UsageMeta::Pos(s) => write!(f, "<{}>", s),
-            UsageMeta::StrictPos(s) => write!(f, "-- <{}>", s),
-        }
-    }
-}
diff --git a/src/meta_youmean.rs b/src/meta_youmean.rs
index be15b41..55107d4 100644
--- a/src/meta_youmean.rs
+++ b/src/meta_youmean.rs
@@ -144,6 +144,8 @@ fn collect_suggestions<'a>(
         Meta::Item(item) => inner_item(arg, item, variants, at_top_level),
         Meta::HideUsage(meta)
         | Meta::Optional(meta)
+        | Meta::Required(meta)
+        | Meta::Anywhere(meta)
         | Meta::Many(meta)
         | Meta::Decorated(meta, _, _) => {
             collect_suggestions(arg, meta, variants, at_top_level);
diff --git a/src/params.rs b/src/params.rs
index 06f6cc4..08973e5 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -61,7 +61,9 @@
 use std::{ffi::OsString, marker::PhantomData, str::FromStr};
 
 use super::{Args, Error, OptionParser, Parser};
-use crate::{args::Arg, from_os_str::parse_os_str, item::ShortLong, Item, Meta};
+use crate::{
+    args::Arg, from_os_str::parse_os_str, item::ShortLong, meta_help::Metavar, Item, Meta,
+};
 
 /// A named thing used to create [`flag`](NamedArg::flag), [`switch`](NamedArg::switch) or
 /// [`argument`](NamedArg::argument)
@@ -687,7 +689,7 @@ impl<T> ParseArgument<T> {
     fn item(&self) -> Item {
         Item::Argument {
             name: ShortLong::from(&self.named),
-            metavar: self.metavar,
+            metavar: Metavar(self.metavar),
             env: self.named.env.first().copied(),
             help: self.named.help.clone(),
             shorts: self.named.short.clone(),
@@ -862,7 +864,7 @@ impl<T> ParsePositional<T> {
 
     fn meta(&self) -> Meta {
         Meta::from(Item::Positional {
-            metavar: self.metavar,
+            metavar: Metavar(self.metavar),
             help: self.help.clone(),
             strict: self.strict,
         })
@@ -875,13 +877,13 @@ fn parse_word(
     metavar: &'static str,
     help: &Option<String>,
 ) -> Result<OsString, Error> {
-    if let Some((is_strict, word)) = args.take_positional_word(metavar)? {
+    if let Some((is_strict, word)) = args.take_positional_word(Metavar(metavar))? {
         if strict && !is_strict {
             #[cfg(feature = "autocomplete")]
             args.push_value("--", &Some("-- Positional only items".to_owned()), false);
 
             return Err(Error::Message(
-                format!("Expected <{}> to be on the right side of --", metavar,),
+                format!("Expected <{}> to be on the right side of --", metavar),
                 false,
             ));
         }
@@ -899,7 +901,7 @@ fn parse_word(
         }
 
         let item = Item::Positional {
-            metavar,
+            metavar: Metavar(metavar),
             help: help.clone(),
             strict,
         };
@@ -928,7 +930,7 @@ where
 /// Parse the next available item on a command line with no restrictions, created with [`any`].
 pub struct ParseAny<T> {
     ty: PhantomData<T>,
-    metavar: &'static str,
+    metavar: Metavar,
     strict: bool,
     help: Option<String>,
 }
@@ -961,7 +963,7 @@ pub struct ParseAny<T> {
 pub fn any<T>(metavar: &'static str) -> ParseAny<T> {
     ParseAny {
         ty: PhantomData,
-        metavar,
+        metavar: Metavar(metavar),
         strict: false,
         help: None,
     }
diff --git a/src/structs.rs b/src/structs.rs
index 08451d4..96b4091 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -119,7 +119,7 @@ where
     }
 
     fn meta(&self) -> Meta {
-        Meta::Many(Box::new(self.inner.meta()))
+        Meta::Many(Box::new(Meta::Required(Box::new(self.inner.meta()))))
     }
 }
 
@@ -569,7 +569,7 @@ where
     }
 
     fn meta(&self) -> Meta {
-        Meta::Many(Box::new(self.inner.meta()))
+        Meta::Many(Box::new(Meta::Optional(Box::new(self.inner.meta()))))
     }
 }
 
@@ -858,7 +858,11 @@ where
                     res
                 }
                 Meta::Item(i) => vec![*i.clone()],
-                Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _, _) => meta_items(m),
+                Meta::Optional(m)
+                | Meta::Required(m)
+                | Meta::Many(m)
+                | Meta::Anywhere(m) // TODO?
+                | Meta::Decorated(m, _, _) => meta_items(m),
                 Meta::Skip | Meta::HideUsage(_) => Vec::new(),
             }
         }
@@ -989,5 +993,5 @@ fn classify_anywhere(meta: Meta) -> Meta {
             });
         }
     }
-    meta
+    Meta::Anywhere(Box::new(meta))
 }


Test patch:
diff --git a/tests/adjacent.rs b/tests/adjacent.rs
index 83f5a3c..1f02b40 100644
--- a/tests/adjacent.rs
+++ b/tests/adjacent.rs
@@ -68,7 +68,7 @@ fn adjacent_error_message_arg_single() {
         .unwrap_stderr();
     assert_eq!(
         r,
-        "Expected -b B, got \"10\". Pass --help for usage information"
+        "Expected -b <B>, got \"10\". Pass --help for usage information"
     );
 }
 
@@ -106,6 +106,6 @@ fn adjacent_error_message_arg_many() {
         .unwrap_stderr();
     assert_eq!(
         r,
-        "Expected -b B, got \"-a\". Pass --help for usage information"
+        "Expected -b <B>, got \"-a\". Pass --help for usage information"
     );
 }
diff --git a/tests/anywhere.rs b/tests/anywhere.rs
index 91de4a7..d3df13e 100644
--- a/tests/anywhere.rs
+++ b/tests/anywhere.rs
@@ -1,5 +1,27 @@
 use bpaf::*;
 
+#[test]
+fn parse_anywhere_positional() {
+    let a = any::<String>("x")
+        .guard(|h| h != "--help", "ignore help")
+        .anywhere();
+
+    let b = short('b').switch();
+    let parser = construct!(a, b).to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+
+    assert_eq!(
+        r,
+        "Usage: <x> [-b]\n\nAvailable options:\n    -b\n    -h, --help  Prints help information\n"
+    );
+    // this should be allowed because "anywhere" prevents anything inside from being positional
+    parser.check_invariants(true);
+}
+
 #[test]
 fn parse_anywhere_no_catch() {
     let a = short('a').req_flag(());
@@ -83,7 +105,7 @@ fn parse_anywhere_catch_required() {
         .unwrap_stderr();
     assert_eq!(
         r,
-        "Expected -a x, got \"-a\". Pass --help for usage information"
+        "Expected -a <x>, got \"-a\". Pass --help for usage information"
     );
 
     let r = parser
@@ -101,7 +123,7 @@ fn parse_anywhere_catch_required() {
         .unwrap_stderr();
     assert_eq!(
         r,
-        "Expected -a x, got \"-c\". Pass --help for usage information"
+        "Expected -a <x>, got \"-c\". Pass --help for usage information"
     );
 
     let r = parser
@@ -110,7 +132,7 @@ fn parse_anywhere_catch_required() {
         .unwrap_stderr();
     assert_eq!(
         r,
-        "Expected -a x, got \"-a\". Pass --help for usage information"
+        "Expected -a <x>, got \"-a\". Pass --help for usage information"
     );
 
     let r = parser
@@ -119,7 +141,7 @@ fn parse_anywhere_catch_required() {
         .unwrap_stderr();
     assert_eq!(
         r,
-        "Expected -a x, got \"-a\". Pass --help for usage information"
+        "Expected -a <x>, got \"-a\". Pass --help for usage information"
     );
 
     let r = parser
diff --git a/tests/manpage.rs b/tests/manpage.rs
index b2f96e3..2827ae5 100644
--- a/tests/manpage.rs
+++ b/tests/manpage.rs
@@ -19,7 +19,7 @@ fn simple_manpage() {
             env!("CARGO_PKG_REPOSITORY"),
         );
 
-    let expected = ".ie \\n(.g .ds Aq \\(aq\n.el .ds Aq '\n.TH simple 1 \"Aug 2022\" - \n.SH NAME\n\nsimple \\- I am a program and I do things\n.SH SYNOPSIS\n\n\\fBsimple\\fR [\\-\\fBd\\fR]\n.SH DESCRIPTION\nSometimes they even work.\n.br\n\nBeware `\\-d`, dragons be here\n.br\n\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-d\\fR, \\fB\\-\\-kraken\\fR\nUnleash the kraken\n.SH AUTHORS\nMichael Baykov <manpacket@gmail.com>\n.SH \"REPORTING BUGS\"\nhttps://github.com/pacak/bpaf\n";
+    let expected = ".ie \\n(.g .ds Aq \\(aq\n.el .ds Aq '\n.TH simple 1 \"Aug 2022\" - \n.SH NAME\n\nsimple \\- I am a program and I do things\n.SH SYNOPSIS\n\n\\fBsimple\\fR [\\fB\\-d\\fR]\n.SH DESCRIPTION\nSometimes they even work.\n.br\n\nBeware `\\-d`, dragons be here\n.br\n\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-d\\fR, \\fB\\-\\-kraken\\fR\nUnleash the kraken\n.SH AUTHORS\nMichael Baykov <manpacket@gmail.com>\n.SH \"REPORTING BUGS\"\nhttps://github.com/pacak/bpaf\n";
     assert_eq!(manpage, expected);
 }
 
@@ -68,7 +68,7 @@ fn nested_command_manpage() {
             env!("CARGO_PKG_REPOSITORY"),
         );
 
-    let expected = ".ie \\n(.g .ds Aq \\(aq\n.el .ds Aq '\n.TH nested 1 \"29 Nov 2022\" - \n.SH NAME\n\nnested \\- I am a program and I do things 3\n.SH SYNOPSIS\n\n\\fBnested\\fR \\-\\fBd\\fR=\\fID\\fR \\fIC\\fR \\fBCOMMAND\\fR...\n.SH DESCRIPTION\nSometimes they even work. 3\n.br\n\nBeware `\\-d`, dragons be here 3\n.br\n\n.SS \"Positional items\"\n.TP\n\n\\fIC\\fR\nMystery file\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-d\\fR, \\fB\\-\\-ddd\\fR=\\fID\\fR\nmystery arg\n.SS \"List of all the subcommands\"\n.TP\n\n\\fBnested\\fR \\fBcmd\\fR\nI am a program and I do things\n.TP\n\n\\fBnested\\fR \\fBdmc\\fR\nI am a program and I do things 2\n.SH \"SUBCOMMANDS WITH OPTIONS\"\n.SS \"nested cmd\"\nI am a program and I do things\n.SS Description\nSometimes they even work. 1\n.br\n\nBeware `\\-d`, dragons be here 1\n.br\n\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-d\\fR=\\fIy\\fR\ndragon\n.SS \"nested dmc, d\"\nI am a program and I do things 2\n.SS Description\nSometimes they even work. 2\n.br\n\nBeware `\\-d`, dragons be here 2\n.br\n\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-k\\fR=\\fIx\\fR\nkraken\n.SH AUTHORS\nMichael Baykov <manpacket@gmail.com>\n.SH \"REPORTING BUGS\"\nhttps://github.com/pacak/bpaf\n";
+    let expected = ".ie \\n(.g .ds Aq \\(aq\n.el .ds Aq '\n.TH nested 1 \"29 Nov 2022\" - \n.SH NAME\n\nnested \\- I am a program and I do things 3\n.SH SYNOPSIS\n\n\\fBnested\\fR \\fB\\-d\\fR=\\fID\\fR \\fIC\\fR \\fBCOMMAND ...\\fR\n.SH DESCRIPTION\nSometimes they even work. 3\n.br\n\nBeware `\\-d`, dragons be here 3\n.br\n\n.SS \"Positional items\"\n.TP\n\n\\fIC\\fR\nMystery file\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-d\\fR, \\fB\\-\\-ddd\\fR=\\fID\\fR\nmystery arg\n.SS \"List of all the subcommands\"\n.TP\n\n\\fBnested\\fR \\fBcmd\\fR\nI am a program and I do things\n.TP\n\n\\fBnested\\fR \\fBdmc\\fR\nI am a program and I do things 2\n.SH \"SUBCOMMANDS WITH OPTIONS\"\n.SS \"nested cmd\"\nI am a program and I do things\n.SS Description\nSometimes they even work. 1\n.br\n\nBeware `\\-d`, dragons be here 1\n.br\n\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-d\\fR=\\fIy\\fR\ndragon\n.SS \"nested dmc, d\"\nI am a program and I do things 2\n.SS Description\nSometimes they even work. 2\n.br\n\nBeware `\\-d`, dragons be here 2\n.br\n\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-k\\fR=\\fIx\\fR\nkraken\n.SH AUTHORS\nMichael Baykov <manpacket@gmail.com>\n.SH \"REPORTING BUGS\"\nhttps://github.com/pacak/bpaf\n";
     assert_eq!(manpage, expected);
 }
 
@@ -99,6 +99,6 @@ fn very_nested_command() {
             env!("CARGO_PKG_REPOSITORY"),
         );
 
-    let expected = ".ie \\n(.g .ds Aq \\(aq\n.el .ds Aq '\n.TH nested 1 \"29 Nov 2022\" - \n.SH NAME\n\nnested\n.SH SYNOPSIS\n\n\\fBnested\\fR \\fBCOMMAND\\fR...\n.SH DESCRIPTION\n.SS \"List of all the subcommands\"\n.TP\n\n\\fBnested\\fR \\fBlvl1\\fR\nlvl 1 description\n.TP\n\n\\fBnested lvl1\\fR \\fBlvl2\\fR\nlvl 2 description\n.TP\n\n\\fBnested lvl1 lvl2\\fR \\fBlvl3\\fR\nlvl 3 description\n.TP\n\n\\fBnested lvl1 lvl2 lvl3\\fR \\fBlvl4\\fR\nlvl 4 description\n.SH \"SUBCOMMANDS WITH OPTIONS\"\n.SS \"nested lvl1\"\nlvl 1 description\n.SS \"nested lvl1 lvl2\"\nlvl 2 description\n.SS \"nested lvl1 lvl2 lvl3\"\nlvl 3 description\n.SS \"nested lvl1 lvl2 lvl3 lvl4\"\nlvl 4 description\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-k\\fR\nUnleash the Kraken\n.SH AUTHORS\nMichael Baykov <manpacket@gmail.com>\n.SH \"REPORTING BUGS\"\nhttps://github.com/pacak/bpaf\n";
+    let expected = ".ie \\n(.g .ds Aq \\(aq\n.el .ds Aq '\n.TH nested 1 \"29 Nov 2022\" - \n.SH NAME\n\nnested\n.SH SYNOPSIS\n\n\\fBnested\\fR \\fBCOMMAND ...\\fR\n.SH DESCRIPTION\n.SS \"List of all the subcommands\"\n.TP\n\n\\fBnested\\fR \\fBlvl1\\fR\nlvl 1 description\n.TP\n\n\\fBnested lvl1\\fR \\fBlvl2\\fR\nlvl 2 description\n.TP\n\n\\fBnested lvl1 lvl2\\fR \\fBlvl3\\fR\nlvl 3 description\n.TP\n\n\\fBnested lvl1 lvl2 lvl3\\fR \\fBlvl4\\fR\nlvl 4 description\n.SH \"SUBCOMMANDS WITH OPTIONS\"\n.SS \"nested lvl1\"\nlvl 1 description\n.SS \"nested lvl1 lvl2\"\nlvl 2 description\n.SS \"nested lvl1 lvl2 lvl3\"\nlvl 3 description\n.SS \"nested lvl1 lvl2 lvl3 lvl4\"\nlvl 4 description\n.SS \"Option arguments and flags\"\n.TP\n\n\\fB\\-k\\fR\nUnleash the Kraken\n.SH AUTHORS\nMichael Baykov <manpacket@gmail.com>\n.SH \"REPORTING BUGS\"\nhttps://github.com/pacak/bpaf\n";
     assert_eq!(manpage, expected);
 }
diff --git a/tests/meta_usage.rs b/tests/meta_usage.rs
index 0c87c24..a7d0fe2 100644
--- a/tests/meta_usage.rs
+++ b/tests/meta_usage.rs
@@ -25,18 +25,25 @@ fn optional_group_meta() {
 }
 
 #[test]
-fn sensors_meta() {
+fn sensors_many() {
     let a = short('a').argument::<String>("A");
     let b = short('b').argument::<String>("B");
     let parser = construct!(a, b).many().to_options();
+    assert_usage(parser, "[-a A -b B]...");
+}
 
+#[test]
+fn sensors_some() {
+    let a = short('a').argument::<String>("A");
+    let b = short('b').argument::<String>("B");
+    let parser = construct!(a, b).some("want some sensors").to_options();
     assert_usage(parser, "(-a A -b B)...");
 }
 
 #[test]
 fn many_arg() {
     let parser = short('a').argument::<String>("A").many().to_options();
-    assert_usage(parser, "-a A...");
+    assert_usage(parser, "[-a A]...");
 }
 
 #[test]
@@ -54,7 +61,6 @@ fn many_switch() {
 #[test]
 fn some_switch() {
     let parser = short('a').switch().some("ARG").to_options();
-    // TODO - not ideal, should be -a... or (-a)...
     assert_usage(parser, "[-a]...");
 }
 
@@ -170,7 +176,7 @@ fn required_one_many() {
     let a = short('a').req_flag(());
     let b = short('b').req_flag(());
     let parser = construct!(a, b).many().to_options();
-    assert_usage(parser, "(-a -b)...");
+    assert_usage(parser, "[-a -b]...");
 }
 
 #[test]
@@ -193,7 +199,7 @@ fn required_or_many() {
     let f = pure(((), ()));
     let ef = construct!([e, f]);
     let parser = construct!([ab, cd, ef]).many().to_options();
-    assert_usage(parser, "(-a -b | -c -d)...");
+    assert_usage(parser, "[-a -b | -c -d]...");
 }
 
 #[test]
@@ -207,7 +213,7 @@ fn no_actual_arguments_also_works() {
         .unwrap_stdout();
     assert_eq!(
         r,
-        "\nAvailable options:\n    -h, --help  Prints help information\n"
+        "Usage: no parameters expected\n\nAvailable options:\n    -h, --help  Prints help information\n"
     );
 
     let x = pure(true).meta();
@@ -222,6 +228,12 @@ fn a_or_b() {
     assert_usage(parser, "(-a A | -b B)");
 }
 
+#[test]
+fn some_req_flag() {
+    let parser = short('a').req_flag(()).some("some").to_options();
+    assert_usage(parser, "-a...");
+}
+
 #[test]
 fn a_or_b_and_c() {
     let a = short('a').long("aaa").argument::<String>("A");
diff --git a/tests/tests.rs b/tests/tests.rs
index 6e07934..27a39c1 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -714,7 +714,7 @@ Available options:
         let expected_help = "\
 add files to the staging area
 
-Usage: [-i] [--all] <FILE>...
+Usage: [-i] [--all] [<FILE>]...
 
 Available options:
     -i
@@ -949,6 +949,7 @@ fn default_plays_nicely_with_command() {
     let expected_help = "\
 inner
 
+Usage: no parameters expected
 
 Available options:
     -h, --help  Prints help information
@@ -1009,6 +1010,7 @@ Available commands:
     let expected_help = "\
 inner descr
 
+Usage: no parameters expected
 
 Available options:
     -h, --help  Prints help information
@@ -1793,7 +1795,9 @@ fn empty_struct() {
         .unwrap_stdout();
     assert_eq!(
         r,
-        "
+        "\
+Usage: no parameters expected
+
 Available options:
     -h, --help  Prints help information
 "
@@ -1815,7 +1819,9 @@ fn empty_tuple() {
         .unwrap_stdout();
     assert_eq!(
         r,
-        "
+        "\
+Usage: no parameters expected
+
 Available options:
     -h, --help  Prints help information
 "
@@ -1911,7 +1917,7 @@ fn option_requires_other_option1() {
     // error message sucks...
     assert_eq!(
         r,
-        "Expected -b B, got \"-a\". Pass --help for usage information"
+        "Expected -b <B>, got \"-a\". Pass --help for usage information"
     );
 }