Repository: pacak/bpaf
Pull request #205 (2023-05-09 00:40:07)

anywhere, any and adjacent refactor
- `any` now takes a validation function to check if it matches an argument or not. This helps to improve quality of error messages.
- `anywhere` is now a method on `ParseAny` (created with `any`) instead of being a member of `Parser` trait and now serves purpose of of matching arbitrary parsers in both positional and flag spaces. Old behavior of `anywhere` wasn't consistent with respect of multiple flags and can be mostly replicated with `adjacent`. Narrowing down scope of `anywhere` helps with rendering help messages.
- `adjacent` is now two separate methods method on `ParseCon` (created with `construct!`) and `ParseCommand` (created with `command`) rather than a member of `Parser` trait. This is mostly to improve help rendering
- a bunch of changes to how help messages are rendered
- for every error there's an existing scope and various indices - should make it easy to plug in `miette` or similar

All of this is mostly to improve error messages and rendered help and reduce inconsistencies.


Patch:
diff --git a/.github/workflows/check-and-lint.yaml b/.github/workflows/check-and-lint.yaml
index f3fdf7c..aeab57a 100644
--- a/.github/workflows/check-and-lint.yaml
+++ b/.github/workflows/check-and-lint.yaml
@@ -140,16 +140,16 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: -p bpaf --all-targets --no-default-features
+          args: -p bpaf --lib --tests --no-default-features
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: -p bpaf --all-targets --all-features
+          args: -p bpaf --lib --tests --all-features
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: -p bpaf_derive --all-targets --no-default-features
+          args: -p bpaf_derive --lib --tests --no-default-features
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: -p bpaf_derive --all-targets --all-features
+          args: -p bpaf_derive --lib --tests --all-features
diff --git a/Changelog.md b/Changelog.md
index 827569b..b97461d 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,6 +1,19 @@
 # Change Log
 
-## bpaf [0.8.0], bpaf_derive [0.3.6] - Unreleased
+## bpaf [0.8.0], bpaf_derive [0.4.0] - Unreleased
+
+### Breaking changes
+
+- `any` now takes a function that checks if it matches the input or not.
+  You can still apply usual filtering with `guard`, etc after it but initial
+  filtering inside a function leads to better error messages.
+- `anywhere` is now a method on `any` instead of being a parser method
+  and should now be used to make an arbitrary looking flag like parsers.
+  You can still parse blocks from arbitrary places using remaining `adjacent`
+  method
+
+TODO: explain catch which is now on optional/some/many
+
 - `many` and `some` will now collect one result from a parser
   that does not consume anything from an argument list allowing
   for easier composition with parsers that consume from both
diff --git a/bpaf_derive/src/field.rs b/bpaf_derive/src/field.rs
index 1738553..51cc32c 100644
--- a/bpaf_derive/src/field.rs
+++ b/bpaf_derive/src/field.rs
@@ -30,7 +30,7 @@ pub enum Name {
 
 #[derive(Debug, Clone)]
 enum ConsumerAttr {
-    Any(LitStr, Option<Box<Type>>),
+    Any(LitStr, Option<Box<Type>>, Option<Box<Expr>>),
     Arg(LitStr, Option<Box<Type>>),
     Pos(LitStr, Option<Box<Type>>),
     Switch,
@@ -229,10 +229,12 @@ impl ToTokens for ConsumerAttr {
         match self {
             ConsumerAttr::Arg(arg, Some(ty)) => quote!(argument::<#ty>(#arg)),
             ConsumerAttr::Pos(arg, Some(ty)) => quote!(positional::<#ty>(#arg)),
-            ConsumerAttr::Any(arg, Some(ty)) => quote!(any::<#ty>(#arg)),
             ConsumerAttr::Arg(arg, None) => quote!(argument(#arg)),
             ConsumerAttr::Pos(arg, None) => quote!(positional(#arg)),
-            ConsumerAttr::Any(arg, None) => quote!(any(#arg)),
+            ConsumerAttr::Any(arg, Some(ty), Some(ex)) => quote!(any::<#ty, _, _>(#arg, #ex)),
+            ConsumerAttr::Any(arg, None, Some(ex)) => quote!(any(#arg, #ex)),
+            ConsumerAttr::Any(arg, Some(ty), None) => quote!(any::<#ty, _, _>(#arg, Some)),
+            ConsumerAttr::Any(arg, None, None) => quote!(any(#arg, Some)),
             ConsumerAttr::Switch => quote!(switch()),
             ConsumerAttr::Flag(a, b) => quote!(flag(#a, #b)),
             ConsumerAttr::ReqFlag(a) => quote!(req_flag(#a)),
diff --git a/bpaf_derive/src/field/named_field.rs b/bpaf_derive/src/field/named_field.rs
index 989bab1..f3ca5e1 100644
--- a/bpaf_derive/src/field/named_field.rs
+++ b/bpaf_derive/src/field/named_field.rs
@@ -214,8 +214,25 @@ impl Field {
                             None
                         };
 
-                        let arg = parse_optional_arg(input)?;
-                        res.consumer = Some(ConsumerAttr::Any(arg, ty));
+                        let arg;
+                        let expr;
+
+                        let content;
+                        if input.peek(syn::token::Paren) {
+                            let _ = parenthesized!(content in input);
+                            arg = content.parse::<LitStr>()?;
+                            if content.peek(token::Comma) {
+                                content.parse::<token::Comma>()?;
+                                expr = Some(Box::new(content.parse::<Expr>()?));
+                            } else {
+                                expr = None;
+                            }
+                        } else {
+                            arg = LitStr::new("ARG", Span::call_site());
+                            expr = None;
+                        }
+
+                        res.consumer = Some(ConsumerAttr::Any(arg, ty, expr));
                     } else if keyword == "switch" {
                         check_stage(&mut stage, 2, &keyword)?;
                         res.consumer = Some(ConsumerAttr::Switch);
@@ -353,7 +370,7 @@ impl Field {
         // Do we even need to derive the name here?
         if let Some(cons) = &self.consumer {
             match cons {
-                ConsumerAttr::Any(_, _) | ConsumerAttr::Pos(_, _) => return,
+                ConsumerAttr::Any(_, _, _) | ConsumerAttr::Pos(_, _) => return,
                 ConsumerAttr::Arg(_, _)
                 | ConsumerAttr::Switch
                 | ConsumerAttr::Flag(_, _)
@@ -432,8 +449,12 @@ impl Field {
 
         if let Some(cons) = &self.consumer {
             match cons {
-                ConsumerAttr::Any(l, None) => {
-                    self.consumer = Some(ConsumerAttr::Any(l.clone(), Some(Box::new(ty.clone()))));
+                ConsumerAttr::Any(l, None, check) => {
+                    self.consumer = Some(ConsumerAttr::Any(
+                        l.clone(),
+                        Some(Box::new(ty.clone())),
+                        check.clone(),
+                    ));
                 }
                 ConsumerAttr::Arg(l, None) => {
                     self.consumer = Some(ConsumerAttr::Arg(l.clone(), Some(Box::new(ty.clone()))));
diff --git a/bpaf_derive/src/top.rs b/bpaf_derive/src/top.rs
index 454a5d9..baf7535 100644
--- a/bpaf_derive/src/top.rs
+++ b/bpaf_derive/src/top.rs
@@ -45,8 +45,6 @@ enum BParser {
         name: ConstrName,
         fields: Fields,
         adjacent: bool,
-        anywhere: bool,
-        catch: bool,
         boxed: bool,
     },
     Singleton(Box<ReqFlag>),
@@ -245,8 +243,6 @@ struct Outer {
     shorts: Vec<LitChar>,
     fallback: Option<Box<Expr>>,
     adjacent: bool,
-    anywhere: bool,
-    catch: bool,
     boxed: bool,
 }
 
@@ -263,8 +259,6 @@ impl Outer {
             shorts: Vec::new(),
             fallback: None,
             adjacent: false,
-            anywhere: false,
-            catch: false,
             boxed: false,
         };
 
@@ -306,14 +300,6 @@ impl Outer {
                         res.version = Some(Box::new(ver));
                     } else if keyword == "adjacent" {
                         res.adjacent = true;
-                    } else if keyword == "anywhere" {
-                        res.anywhere = true;
-                    } else if keyword == "catch" {
-                        if !res.anywhere {
-                            return Err(input_copy
-                                .error("catch only makes sense when used with `anywhere`"));
-                        }
-                        res.catch = true;
                     } else if keyword == "boxed" {
                         res.boxed = true;
                     } else if keyword == "command" {
@@ -437,8 +423,6 @@ impl Top {
             name: constr,
             fields,
             adjacent: outer.adjacent,
-            anywhere: outer.anywhere,
-            catch: outer.catch,
             boxed: outer.boxed,
         };
         Ok(Top {
@@ -480,8 +464,6 @@ impl Top {
                     name: constr,
                     fields,
                     adjacent: outer.adjacent,
-                    anywhere: outer.anywhere,
-                    catch: outer.catch,
                     boxed: outer.boxed,
                 }
             } else if let Some(_cmd) = &inner.command {
@@ -489,8 +471,6 @@ impl Top {
                     name: constr,
                     fields: Fields::NoFields,
                     adjacent: outer.adjacent,
-                    anywhere: outer.anywhere,
-                    catch: outer.catch,
                     boxed: outer.boxed,
                 }
             } else {
@@ -628,8 +608,6 @@ impl ToTokens for BParser {
                 name,
                 fields,
                 adjacent,
-                anywhere,
-                catch,
                 boxed,
             } => {
                 if let Fields::NoFields = fields {
@@ -648,12 +626,6 @@ impl ToTokens for BParser {
                 if let Some(fallback) = &name.fallback {
                     quote!(.fallback(#fallback)).to_tokens(tokens);
                 }
-                if *anywhere {
-                    quote!(.anywhere()).to_tokens(tokens);
-                }
-                if *catch {
-                    quote!(.catch()).to_tokens(tokens);
-                }
                 if *boxed {
                     quote!(.boxed()).to_tokens(tokens);
                 }
diff --git a/docs/src/adjacent_0/cases.txt b/docs/src/adjacent_0/cases.txt
index f41c01c..e5ccb63 100644
--- a/docs/src/adjacent_0/cases.txt
+++ b/docs/src/adjacent_0/cases.txt
@@ -12,4 +12,4 @@ Options { multi: [Multi { m: (), val_1: 10, val_2: 20, val_3: 3.1415 }, Multi {
 ? `-s` can't go in the middle as the parser expects the second item
 > -m 10 20 -s 3.1415
 Stderr
-Expected <V3>, got "-m". Pass --help for usage information
+Expected <V3>, got "-s". Pass --help for usage information
diff --git a/docs/src/adjacent_1/cases.txt b/docs/src/adjacent_1/cases.txt
index 6c83089..9773c0c 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>, pass --help for usage information
diff --git a/docs/src/adjacent_2/combine.rs b/docs/src/adjacent_2/combine.rs
index c494e9c..1114690 100644
--- a/docs/src/adjacent_2/combine.rs
+++ b/docs/src/adjacent_2/combine.rs
@@ -44,5 +44,5 @@ fn cmd() -> impl Parser<Cmd> {
 pub fn options() -> OptionParser<Options> {
     let switch = short('s').switch();
     let commands = cmd().many();
-    construct!(Options { commands, switch }).to_options()
+    construct!(Options { switch, commands }).to_options()
 }
diff --git a/docs/src/adjacent_3/combine.rs b/docs/src/adjacent_3/combine.rs
index f578779..c1611a4 100644
--- a/docs/src/adjacent_3/combine.rs
+++ b/docs/src/adjacent_3/combine.rs
@@ -12,11 +12,10 @@ pub struct Options {
 
 fn exec() -> impl Parser<Vec<OsString>> {
     let start = long("exec").req_flag(());
-    let body = any::<OsString>("EXEC")
-        .guard(|s| s != ";", "end marker")
+    let body = any::<OsString, _, _>("EXEC", |s| (s != ";").then_some(s))
         .many()
         .catch();
-    let end = any::<OsString>("TAIL").guard(|s| s == ";", "end marker");
+    let end = any::<OsString, _, _>("TAIL", |s| (s == ";").then_some(()));
     construct!(start, body, end).adjacent().map(|x| x.1)
 }
 
diff --git a/docs/src/any/combine.rs b/docs/src/any/combine.rs
index 8211c1e..d93e4d5 100644
--- a/docs/src/any/combine.rs
+++ b/docs/src/any/combine.rs
@@ -15,9 +15,8 @@ pub fn options() -> OptionParser<Options> {
         .long("turbo")
         .help("Engage the turbo mode")
         .switch();
-    let rest = any::<OsString>("REST")
+    let rest = any::<OsString, _, _>("REST", |x| (x != "--help").then_some(x))
         .help("app will pass anything unused to a child process")
-        .guard(|x| x != "--help", "keep help")
         .many();
     construct!(Options { turbo, rest }).to_options()
 }
diff --git a/docs/src/any/derive.rs b/docs/src/any/derive.rs
index 6f22def..54860fd 100644
--- a/docs/src/any/derive.rs
+++ b/docs/src/any/derive.rs
@@ -10,11 +10,15 @@ pub struct Options {
     #[bpaf(short, long)]
     /// Engage the turbo mode
     turbo: bool,
-    #[bpaf(any("REST"), guard(not_help, "keep help"), many)]
+    #[bpaf(any("REST", not_help), many)]
     /// app will pass anything unused to a child process
     rest: Vec<OsString>,
 }
 
-fn not_help(s: &OsString) -> bool {
-    s != "--help"
+fn not_help(s: OsString) -> Option<OsString> {
+    if s == "--help" {
+        None
+    } else {
+        Some(s)
+    }
 }
diff --git a/docs/src/anywhere/combine.rs b/docs/src/anywhere/combine.rs
index 1cd052c..a16212f 100644
--- a/docs/src/anywhere/combine.rs
+++ b/docs/src/anywhere/combine.rs
@@ -9,30 +9,23 @@ pub struct Options {
     xinerama: bool,
 }
 
-fn toggle_options(name: &'static str, help: &'static str) -> impl Parser<bool> {
-    any::<String>(name)
-        .help(help)
-        .parse(move |s| {
-            let (state, cur_name) = if let Some(rest) = s.strip_prefix('+') {
-                (true, rest)
-            } else if let Some(rest) = s.strip_prefix('-') {
-                (false, rest)
-            } else {
-                return Err(format!("{} is not a toggle option", s));
-            };
-            if cur_name != name {
-                Err(format!("{} is not a known toggle option name", cur_name))
-            } else {
-                Ok(state)
-            }
-        })
-        .anywhere()
-        .catch()
+fn toggle_option(name: &'static str, help: &'static str) -> impl Parser<bool> {
+    any::<String, _, _>(name, move |s: String| {
+        if let Some(rest) = s.strip_prefix('+') {
+            (rest == name).then_some(true)
+        } else if let Some(rest) = s.strip_prefix('-') {
+            (rest == name).then_some(false)
+        } else {
+            None
+        }
+    })
+    .help(help)
+    .anywhere()
 }
 
 pub fn options() -> OptionParser<Options> {
-    let backing = toggle_options("backing", "Backing status").fallback(false);
-    let xinerama = toggle_options("xinerama", "Xinerama status").fallback(true);
+    let backing = toggle_option("backing", "Backing status").fallback(false);
+    let xinerama = toggle_option("xinerama", "Xinerama status").fallback(true);
     let turbo = short('t')
         .long("turbo")
         .help("Engage the turbo mode")
diff --git a/docs/src/anywhere_1/cases.txt b/docs/src/anywhere_1/cases.txt
index 6621917..a74ccc4 100644
--- a/docs/src/anywhere_1/cases.txt
+++ b/docs/src/anywhere_1/cases.txt
@@ -12,4 +12,4 @@ Options { multi_arg: Some(MultiArg { set: (), name: "name", value: "Bob" }), tur
 ? But not in between
 > --set name --turbo Bob
 Stderr
-Expected <ARG>, pass --help for usage information
+Expected <VAL>, got "--turbo". Pass --help for usage information
diff --git a/docs/src/anywhere_1/combine.rs b/docs/src/anywhere_1/combine.rs
index 25bd16f..ed5410d 100644
--- a/docs/src/anywhere_1/combine.rs
+++ b/docs/src/anywhere_1/combine.rs
@@ -19,10 +19,10 @@ pub struct MultiArg {
 
 pub fn options() -> OptionParser<Options> {
     let set = long("set").req_flag(());
-    let name = positional("ARG");
-    let value = positional("ARG");
+    let name = positional("NAME").help("Name for the option");
+    let value = positional("VAL").help("Value to set");
     let multi_arg = construct!(MultiArg { set, name, value })
-        .anywhere()
+        .adjacent()
         .optional();
 
     let turbo = long("turbo").switch();
diff --git a/docs/src/anywhere_1/derive.rs b/docs/src/anywhere_1/derive.rs
index 67c51e1..718a367 100644
--- a/docs/src/anywhere_1/derive.rs
+++ b/docs/src/anywhere_1/derive.rs
@@ -11,14 +11,16 @@ pub struct Options {
 }
 
 #[derive(Debug, Clone, Bpaf)]
-#[bpaf(anywhere)]
+#[bpaf(adjacent)]
 //
 #[allow(dead_code)]
 pub struct MultiArg {
     #[bpaf(long)]
     set: (),
-    #[bpaf(positional)]
+    #[bpaf(positional("NAME"))]
+    /// Name for the option
     name: String,
-    #[bpaf(positional)]
+    #[bpaf(positional("VAL"))]
+    /// Value to set
     value: String,
 }
diff --git a/docs/src/command/cases.txt b/docs/src/command/cases.txt
index b02505e..3a65902 100644
--- a/docs/src/command/cases.txt
+++ b/docs/src/command/cases.txt
@@ -20,7 +20,7 @@ OK
 Options { flag: true, cmd: Cmd { flag: true, arg: 42 } }
 
 ? And that's the confusing part - unless you add context restrictions with
-? [`adjacent`](Parser::adjacent) and parse command first - outer flag wins.
+? [`adjacent`](crate::ParseCon::adjacent) and parse command first - outer flag wins.
 ? So it's best not to mix names on different levels
 > cmd --arg 42 --flag
 OK
diff --git a/docs/src/find/cases.txt b/docs/src/find/cases.txt
index 453ccb1..2d6a827 100644
--- a/docs/src/find/cases.txt
+++ b/docs/src/find/cases.txt
@@ -29,11 +29,18 @@ 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>]...
-
-Available positional items:
-    -exec  -exec /path/to/command flags and options ;
-    -mode  -mode (perm | -perm | /perm)
+Usage: [-exec [<ITEM>]... ;] [-user <USER>] [-mode <MODE>] [<PATH>]...
 
 Available options:
+  -exec [<ITEM>]... ;
+    -exec       for every file find finds execute a separate shell command
+    <ITEM>      command with its arguments, find will replace {} with a file name
+    ;           anything after literal ";" will be considered a regular option again
+
+  -user <USER>
+    <USER>      User name
+
+  -mode <MODE>
+    <MODE>      (perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw
+
     -h, --help  Prints help information
diff --git a/docs/src/xorg/cases.txt b/docs/src/xorg/cases.txt
index f328bde..9ef0fb0 100644
--- a/docs/src/xorg/cases.txt
+++ b/docs/src/xorg/cases.txt
@@ -13,14 +13,13 @@ 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) <EXT>]...
-
-Available positional items:
-    +ext  enable ext <EXT>
-    -ext  disable ext <EXT>
+Usage: [-t] [(+|-)backing] [(+|-)xinerama] [(+|-)ext <EXT>]...
 
 Available options:
-    -t, --turbo  Engage the turbo mode
-    <backing>    Backing status
-    <xinerama>   Xinerama status
-    -h, --help   Prints help information
+    -t, --turbo    Engage the turbo mode
+    (+|-)backing   Set backing status
+    (+|-)xinerama  Set Xinerama status
+  (+|-)ext <EXT>
+    <EXT>          Extension to enable or disable, see documentation for the full list
+
+    -h, --help     Prints help information
diff --git a/examples/dd.rs b/examples/dd.rs
index 315ae97..f9368e3 100644
--- a/examples/dd.rs
+++ b/examples/dd.rs
@@ -12,21 +12,20 @@ pub struct Options {
     block_size: usize,
 }
 
+/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
 fn tag<T>(name: &'static str, meta: &'static str, help: &'static str) -> impl Parser<T>
 where
     T: FromStr,
     <T as std::str::FromStr>::Err: std::fmt::Display,
 {
-    // it is possible to parse OsString here and strip the prefix with os_str_bytes or a similar
-    // crate
-    any::<String>(meta)
-        .help(help)
-        .parse::<_, _, String>(move |s| match s.strip_prefix(name) {
-            None => Err("Wrong tag".to_string()),
-            Some(body) => T::from_str(body).map_err(|e| e.to_string()),
-        })
-        .anywhere()
-        .catch()
+    // it is possible to parse OsString here and strip the prefix with os_str_bytes or
+    // a similar crate
+    any(meta, move |s: String| {
+        Some(s.strip_prefix(name)?.to_owned())
+    })
+    .help(help)
+    .anywhere()
+    .parse(|s| s.parse())
 }
 
 fn in_file() -> impl Parser<String> {
diff --git a/examples/find.rs b/examples/find.rs
index fc622e8..99fb00b 100644
--- a/examples/find.rs
+++ b/examples/find.rs
@@ -28,31 +28,30 @@ pub struct Options {
 
 // Parses -user xxx
 fn user() -> impl Parser<Option<String>> {
-    let tag = any::<String>("TAG")
-        .guard(|s| s == "-user", "not user")
-        .hide();
-    let value = positional::<String>("USER");
+    // match only literal "-user"
+    let tag = literal("-user").anywhere();
+    let value = positional("USER").help("User name");
     construct!(tag, value)
-        .anywhere()
-        .catch()
+        .adjacent()
         .map(|pair| pair.1)
         .optional()
 }
 
 // parsers -exec xxx yyy zzz ;
 fn exec() -> impl Parser<Option<Vec<OsString>>> {
-    let tag = any::<String>("-exec")
-        .help("-exec /path/to/command flags and options ;")
-        .guard(|s| s == "-exec", "not find");
-    let item = any::<OsString>("ITEM")
-        .guard(|s| s != ";", "not word")
-        .many()
-        .catch()
-        .hide();
-    let endtag = any::<String>("END").guard(|s| s == ";", "not eot").hide();
+    let tag = literal("-exec")
+        .help("for every file find finds execute a separate shell command")
+        .anywhere();
+
+    let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
+        .help("command with its arguments, find will replace {} with a file name")
+        .many();
+
+    let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
+        .help("anything after literal \";\" will be considered a regular option again");
+
     construct!(tag, item, endtag)
-        .anywhere()
-        .catch()
+        .adjacent()
         .map(|triple| triple.1)
         .optional()
 }
@@ -72,11 +71,13 @@ fn perm() -> impl Parser<Option<Perm>> {
         Ok(perms)
     }
 
-    let tag = any::<String>("-mode")
-        .help("-mode (perm | -perm | /perm)")
-        .guard(|x| x == "-mode", "");
-    let mode = any::<String>("mode")
-        .parse::<_, _, String>(|s| {
+    let tag = any::<String, _, _>("-mode", |s| (s == "-mode").then_some(())).anywhere();
+
+    // `any` here is used to parse an arbitrary string that can also start with dash (-)
+    // regular positional parser won't work here
+    let mode = any("MODE", Some)
+        .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
+        .parse::<_, _, String>(|s: String| {
             if let Some(m) = s.strip_prefix('-') {
                 Ok(Perm::All(parse_mode(m)?))
             } else if let Some(m) = s.strip_prefix('/') {
@@ -84,12 +85,10 @@ fn perm() -> impl Parser<Option<Perm>> {
             } else {
                 Ok(Perm::Exact(parse_mode(&s)?))
             }
-        })
-        .hide();
+        });
 
     construct!(tag, mode)
-        .anywhere()
-        .catch()
+        .adjacent()
         .map(|pair| pair.1)
         .optional()
 }
diff --git a/examples/xorg.rs b/examples/xorg.rs
index 1217f06..0f9a451 100644
--- a/examples/xorg.rs
+++ b/examples/xorg.rs
@@ -8,58 +8,39 @@ pub struct Options {
     extensions: Vec<(String, bool)>,
 }
 
-fn toggle_options(name: &'static str, help: &'static str) -> impl Parser<bool> {
-    any::<String>(name)
-        .help(help)
-        .parse(move |s| {
-            let (state, cur_name) = if let Some(rest) = s.strip_prefix('+') {
-                (true, rest)
-            } else if let Some(rest) = s.strip_prefix('-') {
-                (false, rest)
-            } else {
-                return Err(format!("{} is not a toggle option", s));
-            };
-            if cur_name != name {
-                Err(format!("{} is not a known toggle option name", cur_name))
-            } else {
-                Ok(state)
-            }
-        })
-        .anywhere()
-        .catch()
+// matches literal name prefixed with - or +
+fn toggle_options(meta: &'static str, name: &'static str, help: &'static str) -> impl Parser<bool> {
+    any(meta, move |s: String| {
+        if let Some(suf) = s.strip_prefix('+') {
+            (suf == name).then_some(true)
+        } else if let Some(suf) = s.strip_prefix('-') {
+            (suf == name).then_some(false)
+        } else {
+            None
+        }
+    })
+    .help(help)
+    .anywhere()
 }
 
+// matches literal +ext and -ext followed by extension name
 fn extension() -> impl Parser<(String, bool)> {
-    let on = any::<String>("+ext")
-        .help("enable ext <EXT>")
-        .parse::<_, _, String>(|s| {
-            if s == "+ext" {
-                Ok(true)
-            } else {
-                Err(String::new())
-            }
-        });
-    let off = any::<String>("-ext")
-        .help("disable ext <EXT>")
-        .parse::<_, _, String>(|s| {
-            if s == "-ext" {
-                Ok(false)
-            } else {
-                Err(String::new())
-            }
-        });
+    let state = any("(+|-)ext", |s: String| match s.as_str() {
+        "-ext" => Some(false),
+        "+ext" => Some(true),
+        _ => None,
+    })
+    .anywhere();
 
-    let state = construct!([on, off]);
-    let name = positional::<String>("EXT");
-    construct!(state, name)
-        .map(|(a, b)| (b, a))
-        .anywhere()
-        .catch()
+    let name = positional::<String>("EXT")
+        .help("Extension to enable or disable, see documentation for the full list");
+    construct!(state, name).adjacent().map(|(a, b)| (b, a))
 }
 
 pub fn options() -> OptionParser<Options> {
-    let backing = toggle_options("backing", "Backing status").fallback(false);
-    let xinerama = toggle_options("xinerama", "Xinerama status").fallback(true);
+    let backing = toggle_options("(+|-)backing", "backing", "Set backing status").fallback(false);
+    let xinerama =
+        toggle_options("(+|-)xinerama", "xinerama", "Set Xinerama status").fallback(true);
     let turbo = short('t')
         .long("turbo")
         .help("Engage the turbo mode")
diff --git a/src/args.rs b/src/args.rs
index 77b4e81..f0f778d 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -2,7 +2,8 @@ use std::ffi::OsString;
 
 pub(crate) use crate::arg::*;
 use crate::{
-    info::Info, item::Item, meta_help::Metavar, parsers::NamedArg, Error, Meta, ParseFailure,
+    error::MissingItem, info::Info, item::Item, meta_help::Metavar, parsers::NamedArg, Error, Meta,
+    ParseFailure,
 };
 
 /// Shows which branch of [`ParseOrElse`] parsed the argument
@@ -70,6 +71,7 @@ mod inner {
 
         #[doc(hidden)]
         /// Used to render an error message for [`parse`][crate::Parser::parse]
+        /// contains an index of a currently consumed item
         pub current: Option<usize>,
 
         #[doc(hidden)]
@@ -96,6 +98,12 @@ mod inner {
 
         /// A way to customize behavior for --help and error handling
         pub(crate) improve_error: super::Improve,
+
+        /// Describes scope current parser will be consuming elements from. Usually it will be
+        /// considering the whole sequence of (unconsumed) arguments, but for "adjacent"
+        /// scope starts on the right of the first consumed item and might end before the end
+        /// of the list, similarly for "commands"
+        scope: Range<usize>,
     }
 
     impl<const N: usize> From<&[&str; N]> for Args {
@@ -150,6 +158,7 @@ mod inner {
             Args {
                 removed: vec![false; vec.len()],
                 remaining: vec.len(),
+                scope: 0..vec.len(),
                 items: Rc::from(vec),
                 current: None,
                 head: usize::MAX,
@@ -211,16 +220,19 @@ mod inner {
     impl<'a> Args {
         /// creates iterator over remaining elements
         pub(crate) fn items_iter(&'a self) -> ArgsIter<'a> {
-            ArgsIter { args: self, cur: 0 }
+            ArgsIter {
+                args: self,
+                cur: self.scope.start,
+            }
         }
 
         pub(crate) fn remove(&mut self, index: usize) {
-            if !self.removed[index] {
+            if self.scope.contains(&index) && !self.removed[index] {
                 self.current = Some(index);
                 self.remaining -= 1;
                 self.head = self.head.min(index);
+                self.removed[index] = true;
             }
-            self.removed[index] = true;
         }
 
         pub(crate) fn is_empty(&self) -> bool {
@@ -240,6 +252,9 @@ mod inner {
         }
 
         pub(crate) fn get(&self, ix: usize) -> Option<&Arg> {
+            if !self.scope.contains(&ix) {
+                return None;
+            }
             let arg = self.items.get(ix)?;
             if *self.removed.get(ix)? {
                 None
@@ -288,87 +303,64 @@ mod inner {
             self
         }
 
-        /// restrict `guess` to the first adjacent block of consumed elements
-        ///
-        /// returns true when either
-        /// - it improved starting point
-        /// - it improved ending point and there are gaps past it
-        pub(crate) fn refine_range(&self, args: &Args, guess: &mut Range<usize>) -> bool {
-            // nothing to refine
-            if self.removed.is_empty() {
-                return false;
-            }
-            // start isn't at the right place, adjust that and retry the parsing
-            if self.removed[guess.start] == args.removed[guess.start] {
-                for (offset, (this, orig)) in self.removed[guess.start..]
-                    .iter()
-                    .zip(args.removed[guess.start..].iter())
-                    .enumerate()
-                {
-                    let ix = offset + guess.start;
-                    if !orig && *this {
-                        guess.start = ix;
-                        return true;
-                    }
-                }
+        /// Narrow down scope of &self to adjacently consumed values compared to original.
+        pub(crate) fn adjacent_scope(&self, original: &Args) -> Option<Range<usize>> {
+            if self.items.is_empty() {
+                return None;
             }
 
-            // at this point start is at the right place, next bpaf needs to set the end to the first
-            // match - point where adjacent parser stopped consuming items
-            let old_end = guess.end;
-            for (offset, (this, orig)) in self.removed[guess.start..]
+            // starting at the beginning of the scope look for the first mismatch
+            let start = self.scope().start;
+            for (mut offset, (this, orig)) in self.removed[start..]
                 .iter()
-                .zip(args.removed[guess.start..].iter())
+                .zip(original.removed[start..].iter())
                 .enumerate()
             {
-                let ix = offset + guess.start;
+                offset += start;
+                // once there's a mismatch we have the scope we are looking for:
+                // all the adjacent items consumed in this. It doesn't make sense to remove it if
+                // it matches the original scope though...
                 if !this && !orig {
-                    guess.end = ix;
-                    break;
+                    let proposed_scope = start..offset;
+                    return if self.scope() != proposed_scope {
+                        Some(proposed_scope)
+                    } else {
+                        None
+                    };
                 }
             }
+            None
+        }
 
-            // no improvements to the end
-            if old_end == guess.end {
-                return false;
+        /// Get a scope for an adjacently available block of item starting at start
+        pub(crate) fn adjacently_available_from(&self, start: usize) -> Option<Range<usize>> {
+            if self.items.is_empty() {
+                return None;
             }
-
-            // at this point check if there are any consumed items past the new end, if there are -
-            // need to rerun the parser
-            for (this, orig) in self.removed[guess.end..old_end]
-                .iter()
-                .zip(args.removed[guess.end..old_end].iter())
-            {
-                if *this && !orig {
-                    return true;
+            for end in start..self.removed.len() {
+                if self.removed.get(end) != Some(&false) {
+                    return Some(start..end);
                 }
             }
-            // otherwise refining - done
-            false
+            Some(start..start)
         }
 
         pub(crate) fn ranges(&self) -> ArgRangesIter {
             ArgRangesIter { args: self, cur: 0 }
         }
 
+        pub(crate) fn scope(&self) -> Range<usize> {
+            self.scope.clone()
+        }
+
         /// Mark everything outside of `range` as removed
-        pub(crate) fn restrict_to_range(&mut self, range: &Range<usize>) {
-            for (ix, removed) in self.removed.iter_mut().enumerate() {
-                if !range.contains(&ix) {
-                    *removed = true;
-                }
-            }
+        pub(crate) fn set_scope(&mut self, scope: Range<usize>) {
+            self.scope = scope;
             self.sync_remaining();
         }
 
         fn sync_remaining(&mut self) {
-            self.remaining = self.removed.iter().filter(|x| !**x).count();
-        }
-
-        /// Copy a range of removals from args to self
-        pub(crate) fn copy_usage_from(&mut self, args: &Args, range: Range<usize>) {
-            self.removed[range.start..range.end].copy_from_slice(&args.removed[range]);
-            self.sync_remaining();
+            self.remaining = self.removed[self.scope()].iter().filter(|x| !**x).count();
         }
 
         #[inline(never)]
@@ -445,14 +437,17 @@ mod inner {
                             self.remaining += 1; // removed Ambiguity, added short and word
                         }
                         (false, false) => {
-                            // can't parse it as neither flag or argument, give up.
-                            // ambiguity will stay and will be reported around meta_youmean
+                            // can't parse it as neither flag or argument, at least directly.
+                            // treat it as a word argument - it might be consumed by `any` or
+                            // reported by meta_youmean later.
+                            new_items[pos] = Arg::Word(std::mem::take(os));
                         }
                     }
                 }
                 pos += 1;
             }
             self.items = Rc::from(new_items);
+            self.scope = 0..self.items.len();
             Ok(())
         }
 
@@ -499,6 +494,9 @@ mod inner {
         fn next(&mut self) -> Option<Self::Item> {
             loop {
                 let cur = self.cur;
+                if cur > self.args.scope.end {
+                    return None;
+                }
                 self.cur += 1;
 
                 if *self.args.removed.get(cur)? {
@@ -506,7 +504,7 @@ mod inner {
                 }
 
                 let mut args = self.args.clone();
-                args.restrict_to_range(&(cur..usize::MAX));
+                args.set_scope(cur..self.args.items.len());
                 return Some((cur, args));
             }
         }
@@ -518,6 +516,9 @@ mod inner {
         fn next(&mut self) -> Option<Self::Item> {
             loop {
                 let ix = self.cur;
+                if !self.args.scope.contains(&ix) {
+                    return None;
+                }
                 self.cur += 1;
                 if !*self.args.removed.get(ix)? {
                     return Some((ix, &self.args.items[ix]));
@@ -608,11 +609,12 @@ impl Args {
                 };
                 return Err(Error::Message(msg, false));
             }
-            _ => {
+            None | Some(Arg::PosWord(_) | Arg::Ambiguity(..)) => {
+                // TODO - this is missing!
                 return Err(Error::Message(
                     format!("{} requires an argument", arg),
                     false,
-                ))
+                ));
             }
         };
         let val = val.clone();
@@ -643,11 +645,19 @@ impl Args {
                 self.remove(ix);
                 Ok(Some((false, w)))
             }
-            Some((_, _arg)) => Err(Error::Missing(vec![Item::Positional {
-                help: None,
-                metavar,
-                strict: false,
-            }])),
+            Some((position, _arg)) => {
+                let missing = MissingItem {
+                    item: Item::Positional {
+                        help: None,
+                        metavar,
+                        strict: false,
+                        anywhere: false,
+                    },
+                    position,
+                    scope: position..position + 1,
+                };
+                Err(Error::Missing(vec![missing]))
+            }
             None => Ok(None),
         }
     }
@@ -657,6 +667,7 @@ impl Args {
         if let Some((ix, Arg::Word(w))) = self.items_iter().next() {
             if w == word {
                 self.remove(ix);
+                self.current = Some(ix);
                 return true;
             }
         }
diff --git a/src/buffer.rs b/src/buffer.rs
index cc926d8..61d9ad0 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -5,7 +5,9 @@
 pub(crate) enum Style {
     /// Plain text, no extra decorations
     Text,
+    /// Section title
     Section,
+    /// Flag name
     Label,
 }
 
@@ -36,6 +38,15 @@ pub(crate) struct Buffer {
 }
 
 impl Buffer {
+    pub(crate) fn clear(&mut self) {
+        self.payload.clear();
+        self.tokens.clear();
+        self.tabstop = 0;
+        self.current_margin = 0;
+        self.current_char = 0;
+        self.complete = false;
+    }
+
     pub(crate) fn tabstop(&mut self) {
         let max = self.tabstop.max(self.current_char);
         if max <= MAX_TAB {
@@ -376,13 +387,18 @@ fn line_breaking_rules() {
     m.write_str("hello\nworld\n", Style::Text);
     assert_eq!(m.to_string(), "hello world");
 
-    let mut m = Buffer::default();
-    m.complete = false;
+    let mut m = Buffer {
+        complete: false,
+        ..Buffer::default()
+    };
     m.write_str("hello\n\nworld", Style::Text);
     assert_eq!(m.to_string(), "hello");
 
-    let mut m = Buffer::default();
-    m.complete = true;
+    let mut m = Buffer {
+        complete: true,
+        ..Buffer::default()
+    };
+
     m.write_str("hello\n\nworld", Style::Text);
     assert_eq!(m.to_string(), "hello\nworld");
 }
diff --git a/src/docs/adjacent_0.md b/src/docs/adjacent_0.md
index 96f64f9..15f7edf 100644
--- a/src/docs/adjacent_0.md
+++ b/src/docs/adjacent_0.md
@@ -91,7 +91,7 @@ Options { multi: [Multi { m: (), val_1: 10, val_2: 20, val_3: 3.1415 }, Multi {
 `-s` can't go in the middle as the parser expects the second item
 ```console
 % app -m 10 20 -s 3.1415
-Expected <V3>, got "-m". Pass --help for usage information
+Expected <V3>, got "-s". Pass --help for usage information
 ```
 
 </details>
diff --git a/src/docs/adjacent_1.md b/src/docs/adjacent_1.md
index 29c21b5..8f65f21 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>, pass --help for usage information
 ```
 
 </details>
diff --git a/src/docs/adjacent_2.md b/src/docs/adjacent_2.md
index 647721d..ce5dabb 100644
--- a/src/docs/adjacent_2.md
+++ b/src/docs/adjacent_2.md
@@ -45,7 +45,7 @@ fn cmd() -> impl Parser<Cmd> {
 pub fn options() -> OptionParser<Options> {
     let switch = short('s').switch();
     let commands = cmd().many();
-    construct!(Options { commands, switch }).to_options()
+    construct!(Options { switch, commands }).to_options()
 }
 ```
 
diff --git a/src/docs/adjacent_3.md b/src/docs/adjacent_3.md
index 0bb490f..0d30b48 100644
--- a/src/docs/adjacent_3.md
+++ b/src/docs/adjacent_3.md
@@ -13,11 +13,10 @@ pub struct Options {
 
 fn exec() -> impl Parser<Vec<OsString>> {
     let start = long("exec").req_flag(());
-    let body = any::<OsString>("EXEC")
-        .guard(|s| s != ";", "end marker")
+    let body = any::<OsString, _, _>("EXEC", |s| (s != ";").then_some(s))
         .many()
         .catch();
-    let end = any::<OsString>("TAIL").guard(|s| s == ";", "end marker");
+    let end = any::<OsString, _, _>("TAIL", |s| (s == ";").then_some(()));
     construct!(start, body, end).adjacent().map(|x| x.1)
 }
 
diff --git a/src/docs/any.md b/src/docs/any.md
index 9b006c9..c3ec07c 100644
--- a/src/docs/any.md
+++ b/src/docs/any.md
@@ -16,9 +16,8 @@ pub fn options() -> OptionParser<Options> {
         .long("turbo")
         .help("Engage the turbo mode")
         .switch();
-    let rest = any::<OsString>("REST")
+    let rest = any::<OsString, _, _>("REST", |x| (x != "--help").then_some(x))
         .help("app will pass anything unused to a child process")
-        .guard(|x| x != "--help", "keep help")
         .many();
     construct!(Options { turbo, rest }).to_options()
 }
@@ -38,13 +37,17 @@ pub struct Options {
     #[bpaf(short, long)]
     /// Engage the turbo mode
     turbo: bool,
-    #[bpaf(any("REST"), guard(not_help, "keep help"), many)]
+    #[bpaf(any("REST", not_help), many)]
     /// app will pass anything unused to a child process
     rest: Vec<OsString>,
 }
 
-fn not_help(s: &OsString) -> bool {
-    s != "--help"
+fn not_help(s: OsString) -> Option<OsString> {
+    if s == "--help" {
+        None
+    } else {
+        Some(s)
+    }
 }
 ```
 
diff --git a/src/docs/anywhere.md b/src/docs/anywhere.md
index 7ae5910..01e3b9c 100644
--- a/src/docs/anywhere.md
+++ b/src/docs/anywhere.md
@@ -11,30 +11,23 @@ pub struct Options {
     xinerama: bool,
 }
 
-fn toggle_options(name: &'static str, help: &'static str) -> impl Parser<bool> {
-    any::<String>(name)
-        .help(help)
-        .parse(move |s| {
-            let (state, cur_name) = if let Some(rest) = s.strip_prefix('+') {
-                (true, rest)
-            } else if let Some(rest) = s.strip_prefix('-') {
-                (false, rest)
-            } else {
-                return Err(format!("{} is not a toggle option", s));
-            };
-            if cur_name != name {
-                Err(format!("{} is not a known toggle option name", cur_name))
-            } else {
-                Ok(state)
-            }
-        })
-        .anywhere()
-        .catch()
+fn toggle_option(name: &'static str, help: &'static str) -> impl Parser<bool> {
+    any::<String, _, _>(name, move |s: String| {
+        if let Some(rest) = s.strip_prefix('+') {
+            (rest == name).then_some(true)
+        } else if let Some(rest) = s.strip_prefix('-') {
+            (rest == name).then_some(false)
+        } else {
+            None
+        }
+    })
+    .help(help)
+    .anywhere()
 }
 
 pub fn options() -> OptionParser<Options> {
-    let backing = toggle_options("backing", "Backing status").fallback(false);
-    let xinerama = toggle_options("xinerama", "Xinerama status").fallback(true);
+    let backing = toggle_option("backing", "Backing status").fallback(false);
+    let xinerama = toggle_option("xinerama", "Xinerama status").fallback(true);
     let turbo = short('t')
         .long("turbo")
         .help("Engage the turbo mode")
diff --git a/src/docs/anywhere_1.md b/src/docs/anywhere_1.md
index a604043..84ce2c9 100644
--- a/src/docs/anywhere_1.md
+++ b/src/docs/anywhere_1.md
@@ -20,10 +20,10 @@ pub struct MultiArg {
 
 pub fn options() -> OptionParser<Options> {
     let set = long("set").req_flag(());
-    let name = positional("ARG");
-    let value = positional("ARG");
+    let name = positional("NAME").help("Name for the option");
+    let value = positional("VAL").help("Value to set");
     let multi_arg = construct!(MultiArg { set, name, value })
-        .anywhere()
+        .adjacent()
         .optional();
 
     let turbo = long("turbo").switch();
@@ -47,14 +47,16 @@ pub struct Options {
 }
 
 #[derive(Debug, Clone, Bpaf)]
-#[bpaf(anywhere)]
+#[bpaf(adjacent)]
 # #[allow(dead_code)]
 pub struct MultiArg {
     #[bpaf(long)]
     set: (),
-    #[bpaf(positional)]
+    #[bpaf(positional("NAME"))]
+    /// Name for the option
     name: String,
-    #[bpaf(positional)]
+    #[bpaf(positional("VAL"))]
+    /// Value to set
     value: String,
 }
 ```
@@ -80,7 +82,7 @@ Options { multi_arg: Some(MultiArg { set: (), name: "name", value: "Bob" }), tur
 But not in between
 ```console
 % app --set name --turbo Bob
-Expected <ARG>, pass --help for usage information
+Expected <VAL>, got "--turbo". Pass --help for usage information
 ```
 
 </details>
diff --git a/src/docs/command.md b/src/docs/command.md
index cecad79..244e3df 100644
--- a/src/docs/command.md
+++ b/src/docs/command.md
@@ -95,7 +95,7 @@ Options { flag: true, cmd: Cmd { flag: true, arg: 42 } }
 ```
 
 And that's the confusing part - unless you add context restrictions with
-[`adjacent`](Parser::adjacent) and parse command first - outer flag wins.
+[`adjacent`](crate::ParseCon::adjacent) and parse command first - outer flag wins.
 So it's best not to mix names on different levels
 ```console
 % app cmd --arg 42 --flag
diff --git a/src/docs/dd.md b/src/docs/dd.md
index f77f06e..141ed1d 100644
--- a/src/docs/dd.md
+++ b/src/docs/dd.md
@@ -13,21 +13,20 @@ pub struct Options {
     block_size: usize,
 }
 
+/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
 fn tag<T>(name: &'static str, meta: &'static str, help: &'static str) -> impl Parser<T>
 where
     T: FromStr,
     <T as std::str::FromStr>::Err: std::fmt::Display,
 {
-    // it is possible to parse OsString here and strip the prefix with os_str_bytes or a similar
-    // crate
-    any::<String>(meta)
-        .help(help)
-        .parse::<_, _, String>(move |s| match s.strip_prefix(name) {
-            None => Err("Wrong tag".to_string()),
-            Some(body) => T::from_str(body).map_err(|e| e.to_string()),
-        })
-        .anywhere()
-        .catch()
+    // it is possible to parse OsString here and strip the prefix with os_str_bytes or
+    // a similar crate
+    any(meta, move |s: String| {
+        Some(s.strip_prefix(name)?.to_owned())
+    })
+    .help(help)
+    .anywhere()
+    .parse(|s| s.parse())
 }
 
 fn in_file() -> impl Parser<String> {
diff --git a/src/docs/find.md b/src/docs/find.md
index 75bd0d4..017eb09 100644
--- a/src/docs/find.md
+++ b/src/docs/find.md
@@ -29,31 +29,30 @@ pub struct Options {
 
 // Parses -user xxx
 fn user() -> impl Parser<Option<String>> {
-    let tag = any::<String>("TAG")
-        .guard(|s| s == "-user", "not user")
-        .hide();
-    let value = positional::<String>("USER");
+    // match only literal "-user"
+    let tag = literal("-user").anywhere();
+    let value = positional("USER").help("User name");
     construct!(tag, value)
-        .anywhere()
-        .catch()
+        .adjacent()
         .map(|pair| pair.1)
         .optional()
 }
 
 // parsers -exec xxx yyy zzz ;
 fn exec() -> impl Parser<Option<Vec<OsString>>> {
-    let tag = any::<String>("-exec")
-        .help("-exec /path/to/command flags and options ;")
-        .guard(|s| s == "-exec", "not find");
-    let item = any::<OsString>("ITEM")
-        .guard(|s| s != ";", "not word")
-        .many()
-        .catch()
-        .hide();
-    let endtag = any::<String>("END").guard(|s| s == ";", "not eot").hide();
+    let tag = literal("-exec")
+        .help("for every file find finds execute a separate shell command")
+        .anywhere();
+
+    let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
+        .help("command with its arguments, find will replace {} with a file name")
+        .many();
+
+    let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
+        .help("anything after literal \";\" will be considered a regular option again");
+
     construct!(tag, item, endtag)
-        .anywhere()
-        .catch()
+        .adjacent()
         .map(|triple| triple.1)
         .optional()
 }
@@ -73,11 +72,13 @@ fn perm() -> impl Parser<Option<Perm>> {
         Ok(perms)
     }
 
-    let tag = any::<String>("-mode")
-        .help("-mode (perm | -perm | /perm)")
-        .guard(|x| x == "-mode", "");
-    let mode = any::<String>("mode")
-        .parse::<_, _, String>(|s| {
+    let tag = any::<String, _, _>("-mode", |s| (s == "-mode").then_some(())).anywhere();
+
+    // `any` here is used to parse an arbitrary string that can also start with dash (-)
+    // regular positional parser won't work here
+    let mode = any("MODE", Some)
+        .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
+        .parse::<_, _, String>(|s: String| {
             if let Some(m) = s.strip_prefix('-') {
                 Ok(Perm::All(parse_mode(m)?))
             } else if let Some(m) = s.strip_prefix('/') {
@@ -85,12 +86,10 @@ fn perm() -> impl Parser<Option<Perm>> {
             } else {
                 Ok(Perm::Exact(parse_mode(&s)?))
             }
-        })
-        .hide();
+        });
 
     construct!(tag, mode)
-        .anywhere()
-        .catch()
+        .adjacent()
         .map(|pair| pair.1)
         .optional()
 }
@@ -152,13 +151,20 @@ 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>]...
-
-Available positional items:
-    -exec  -exec /path/to/command flags and options ;
-    -mode  -mode (perm | -perm | /perm)
+Usage: [-exec [<ITEM>]... ;] [-user <USER>] [-mode <MODE>] [<PATH>]...
 
 Available options:
+  -exec [<ITEM>]... ;
+    -exec       for every file find finds execute a separate shell command
+    <ITEM>      command with its arguments, find will replace {} with a file name
+    ;           anything after literal ";" will be considered a regular option again
+
+  -user <USER>
+    <USER>      User name
+
+  -mode <MODE>
+    <MODE>      (perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw
+
     -h, --help  Prints help information
 ```
 
diff --git a/src/docs/xorg.md b/src/docs/xorg.md
index c72ef7e..4989db5 100644
--- a/src/docs/xorg.md
+++ b/src/docs/xorg.md
@@ -9,58 +9,39 @@ pub struct Options {
     extensions: Vec<(String, bool)>,
 }
 
-fn toggle_options(name: &'static str, help: &'static str) -> impl Parser<bool> {
-    any::<String>(name)
-        .help(help)
-        .parse(move |s| {
-            let (state, cur_name) = if let Some(rest) = s.strip_prefix('+') {
-                (true, rest)
-            } else if let Some(rest) = s.strip_prefix('-') {
-                (false, rest)
-            } else {
-                return Err(format!("{} is not a toggle option", s));
-            };
-            if cur_name != name {
-                Err(format!("{} is not a known toggle option name", cur_name))
-            } else {
-                Ok(state)
-            }
-        })
-        .anywhere()
-        .catch()
+// matches literal name prefixed with - or +
+fn toggle_options(meta: &'static str, name: &'static str, help: &'static str) -> impl Parser<bool> {
+    any(meta, move |s: String| {
+        if let Some(suf) = s.strip_prefix('+') {
+            (suf == name).then_some(true)
+        } else if let Some(suf) = s.strip_prefix('-') {
+            (suf == name).then_some(false)
+        } else {
+            None
+        }
+    })
+    .help(help)
+    .anywhere()
 }
 
+// matches literal +ext and -ext followed by extension name
 fn extension() -> impl Parser<(String, bool)> {
-    let on = any::<String>("+ext")
-        .help("enable ext <EXT>")
-        .parse::<_, _, String>(|s| {
-            if s == "+ext" {
-                Ok(true)
-            } else {
-                Err(String::new())
-            }
-        });
-    let off = any::<String>("-ext")
-        .help("disable ext <EXT>")
-        .parse::<_, _, String>(|s| {
-            if s == "-ext" {
-                Ok(false)
-            } else {
-                Err(String::new())
-            }
-        });
+    let state = any("(+|-)ext", |s: String| match s.as_str() {
+        "-ext" => Some(false),
+        "+ext" => Some(true),
+        _ => None,
+    })
+    .anywhere();
 
-    let state = construct!([on, off]);
-    let name = positional::<String>("EXT");
-    construct!(state, name)
-        .map(|(a, b)| (b, a))
-        .anywhere()
-        .catch()
+    let name = positional::<String>("EXT")
+        .help("Extension to enable or disable, see documentation for the full list");
+    construct!(state, name).adjacent().map(|(a, b)| (b, a))
 }
 
 pub fn options() -> OptionParser<Options> {
-    let backing = toggle_options("backing", "Backing status").fallback(false);
-    let xinerama = toggle_options("xinerama", "Xinerama status").fallback(true);
+    let backing = toggle_options("(+|-)backing", "backing", "Set backing status").fallback(false);
+    let xinerama =
+        toggle_options("(+|-)xinerama", "xinerama", "Set Xinerama status").fallback(true);
     let turbo = short('t')
         .long("turbo")
         .help("Engage the turbo mode")
@@ -101,17 +82,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) <EXT>]...
-
-Available positional items:
-    +ext  enable ext <EXT>
-    -ext  disable ext <EXT>
+Usage: [-t] [(+|-)backing] [(+|-)xinerama] [(+|-)ext <EXT>]...
 
 Available options:
-    -t, --turbo  Engage the turbo mode
-    <backing>    Backing status
-    <xinerama>   Xinerama status
-    -h, --help   Prints help information
+    -t, --turbo    Engage the turbo mode
+    (+|-)backing   Set backing status
+    (+|-)xinerama  Set Xinerama status
+  (+|-)ext <EXT>
+    <EXT>          Extension to enable or disable, see documentation for the full list
+
+    -h, --help     Prints help information
 ```
 
 </details>
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..66e6fed
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,115 @@
+use std::ops::Range;
+
+use crate::item::Item;
+
+/// Unsuccessful command line parsing outcome, internal representation
+#[derive(Debug)]
+pub enum Error {
+    /// Parsing failed, it is still possible to improve the error message
+    /// bool shows if this error can be caught with fallback, messages with it set to true are
+    /// generated by .some(msg) modifier and allow to use it with .fallback
+    Message(String, bool),
+    /// Parsing failed and this is the final output
+    ParseFailure(ParseFailure),
+    /// Expected one of those values
+    ///
+    /// Used internally to generate better error messages
+    Missing(Vec<MissingItem>),
+}
+
+/// Missing item in a context
+#[derive(Debug, Clone)]
+pub struct MissingItem {
+    /// Item that is missing
+    pub(crate) item: Item,
+    /// Position it is missing from - exact for positionals, earliest possible for flags
+    pub(crate) position: usize,
+    /// Range where search was performed, important for combinators that narrow the search scope
+    /// such as adjacent
+    pub(crate) scope: Range<usize>,
+}
+
+impl Error {
+    #[must_use]
+    pub(crate) fn combine_with(self, other: Self) -> Self {
+        #[allow(clippy::match_same_arms)]
+        match (self, other) {
+            // help output takes priority
+            (a @ Error::ParseFailure(_), _) => a,
+            (_, b @ Error::ParseFailure(_)) => b,
+
+            // unconditional parsing failure takes priority
+            (a @ Error::Message(_, _), _) => a,
+            (_, b @ Error::Message(_, _)) => b,
+
+            // combine missing elements
+            (Error::Missing(mut a), Error::Missing(mut b)) => {
+                a.append(&mut b);
+                Error::Missing(a)
+            }
+        }
+    }
+}
+
+/// Unsuccessful command line parsing outcome, use it for unit tests
+///
+/// Useful for unit testing for user parsers, consume it with
+/// [`ParseFailure::unwrap_stdout`] and [`ParseFailure::unwrap_stdout`]
+#[derive(Clone, Debug)]
+pub enum ParseFailure {
+    /// Print this to stdout and exit with success code
+    Stdout(String),
+    /// Print this to stderr and exit with failure code
+    Stderr(String),
+}
+
+impl ParseFailure {
+    /// Returns the contained `stderr` values - for unit tests
+    ///
+    /// # Panics
+    ///
+    /// Panics if failure contains `stdout`
+    #[allow(clippy::must_use_candidate)]
+    #[track_caller]
+    pub fn unwrap_stderr(self) -> String {
+        match self {
+            Self::Stderr(err) => err,
+            Self::Stdout(_) => {
+                panic!("not an stderr: {:?}", self)
+            }
+        }
+    }
+
+    /// Returns the contained `stdout` values - for unit tests
+    ///
+    /// # Panics
+    ///
+    /// Panics if failure contains `stderr`
+    #[allow(clippy::must_use_candidate)]
+    #[track_caller]
+    pub fn unwrap_stdout(self) -> String {
+        match self {
+            Self::Stdout(err) => err,
+            Self::Stderr(_) => {
+                panic!("not an stdout: {:?}", self)
+            }
+        }
+    }
+
+    /// Run an action appropriate to the failure and produce the exit code
+    ///
+    /// Prints a message to `stdout` or `stderr` and returns the exit code
+    #[allow(clippy::must_use_candidate)]
+    pub fn exit_code(self) -> i32 {
+        match self {
+            ParseFailure::Stdout(msg) => {
+                print!("{}", msg); // completions are sad otherwise
+                0
+            }
+            ParseFailure::Stderr(msg) => {
+                eprintln!("{}", msg);
+                1
+            }
+        }
+    }
+}
diff --git a/src/help.rs b/src/help.rs
index 0b7bc24..81d0f24 100644
--- a/src/help.rs
+++ b/src/help.rs
@@ -3,7 +3,8 @@
 //! covers `--help`, `--version` etc.
 
 use crate::{
-    info::Info, item::Item, meta_help::render_help, short, Args, Error, Meta, ParseFailure, Parser,
+    error::MissingItem, info::Info, meta_help::render_help, short, Args, Error, Meta, ParseFailure,
+    Parser,
 };
 
 struct ParseExtraParams {
@@ -115,16 +116,35 @@ pub(crate) fn improve_error(
 }
 
 #[inline(never)]
-pub(crate) fn summarize_missing(items: &[Item], inner: &Meta, args: &Args) -> String {
+pub(crate) fn summarize_missing(items: &[MissingItem], inner: &Meta, args: &Args) -> String {
+    // missing items can belong to different scopes, pick the best scope to work with
+    let (best_pos, mut best_scope) = match items
+        .iter()
+        .max_by_key(|item| (item.position, item.scope.start))
+    {
+        Some(item) => (item.position, item.scope.clone()),
+        None => return "Nothing expected, but parser somehow failed...".to_owned(),
+    };
+
     let meta = Meta::Or(
         items
             .iter()
-            .map(|i| Meta::from(i.clone()))
+            .filter_map(|i| {
+                if i.scope == best_scope {
+                    Some(Meta::from(i.item.clone()))
+                } else {
+                    None
+                }
+            })
             .collect::<Vec<_>>(),
     )
     .normalized(false);
+
+    best_scope.start = best_scope.start.max(best_pos);
+    let mut args = args.clone();
+    args.set_scope(best_scope);
     if let Some(x) = args.peek() {
-        if let Some(msg) = crate::meta_youmean::suggest(args, inner) {
+        if let Some(msg) = crate::meta_youmean::suggest(&args, inner) {
             msg
         } else {
             format!(
diff --git a/src/info.rs b/src/info.rs
index 6293f47..601bb23 100644
--- a/src/info.rs
+++ b/src/info.rs
@@ -2,48 +2,10 @@
 
 use crate::{
     args::{Args, Conflict},
-    item::Item,
     parsers::ParseCommand,
-    ParseFailure, Parser,
+    Error, ParseFailure, Parser,
 };
 
-/// Unsuccessful command line parsing outcome, internal representation
-#[derive(Debug)]
-pub enum Error {
-    /// Parsing failed, it is still possible to improve the error message
-    /// bool shows if this error can be caught with fallback, messages with it set to true are
-    /// generated by .some(msg) modifier and allow to use it with .fallback
-    Message(String, bool),
-    /// Parsing failed and this is the final output
-    ParseFailure(ParseFailure),
-    /// Expected one of those values
-    ///
-    /// Used internally to generate better error messages
-    Missing(Vec<Item>),
-}
-
-impl Error {
-    #[must_use]
-    pub(crate) fn combine_with(self, other: Self) -> Self {
-        #[allow(clippy::match_same_arms)]
-        match (self, other) {
-            // help output takes priority
-            (a @ Error::ParseFailure(_), _) => a,
-            (_, b @ Error::ParseFailure(_)) => b,
-
-            // parsing failure takes priority
-            (a @ Error::Message(_, _), _) => a,
-            (_, b @ Error::Message(_, _)) => b,
-
-            // combine missing elements
-            (Error::Missing(mut a), Error::Missing(mut b)) => {
-                a.append(&mut b);
-                Error::Missing(a)
-            }
-        }
-    }
-}
-
 /// Information about the parser
 ///
 /// No longer public, users are only interacting with it via [`OptionParser`]
@@ -218,6 +180,7 @@ impl<T> OptionParser<T> {
         self.inner
             .meta()
             .collect_shorts(&mut avail_flags, &mut avail_args);
+
         args.disambiguate(&avail_flags, &avail_args)?;
         match self.run_subparser(&mut args) {
             Ok(t) if args.is_empty() => Ok(t),
diff --git a/src/item.rs b/src/item.rs
index 6e5f274..1aa1407 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -3,7 +3,11 @@ use crate::{info::Info, meta_help::Metavar, parsers::NamedArg, Meta};
 #[doc(hidden)]
 #[derive(Clone, Debug)]
 pub enum Item {
+    /// Positional item, consumed from the the front of the arguments
+    /// <FILE>
     Positional {
+        /// used by any, moves it from positionals into arguments
+        anywhere: bool,
         metavar: Metavar,
         strict: bool,
         help: Option<String>,
@@ -15,6 +19,9 @@ pub enum Item {
         meta: Box<Meta>,
         info: Box<Info>,
     },
+    /// short or long name, consumed anywhere
+    /// -f
+    /// --file
     Flag {
         name: ShortLong,
         /// used for disambiguation
@@ -22,6 +29,9 @@ pub enum Item {
         env: Option<&'static str>,
         help: Option<String>,
     },
+    /// Short or long name followed by a value, consumed anywhere
+    /// -f <VAL>
+    /// --file <VAL>
     Argument {
         name: ShortLong,
         /// used for disambiguation
@@ -30,20 +40,14 @@ pub enum Item {
         env: Option<&'static str>,
         help: Option<String>,
     },
-    MultiArg {
-        name: ShortLong,
-        /// used for disambiguation
-        shorts: Vec<char>,
-        help: 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,
+            Item::Positional { anywhere, .. } => !anywhere,
+            Item::Command { .. } => true,
+            Item::Flag { .. } | Item::Argument { .. } => false,
         }
     }
 }
@@ -95,12 +99,13 @@ impl std::fmt::Display for Item {
                 metavar,
                 strict,
                 help: _,
+                anywhere: _,
             } => {
                 if *strict {
                     f.write_str("-- ")?;
                 }
-                // use write! macro to reset the alternative view
                 write!(f, "{}", metavar)
+                //                metavar.fmt(f)
             }
             Item::Command { .. } => f.write_str("COMMAND ..."),
             Item::Flag {
@@ -121,21 +126,6 @@ impl std::fmt::Display for Item {
                 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 3eaa87b..9c6830b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -397,6 +397,7 @@ mod complete_gen;
 mod complete_run;
 #[cfg(feature = "autocomplete")]
 mod complete_shell;
+mod error;
 mod help;
 mod info;
 mod item;
@@ -411,7 +412,9 @@ mod structs;
 mod tests;
 
 #[doc(hidden)]
-pub use crate::info::Error;
+pub use crate::error::Error;
+#[doc(inline)]
+pub use crate::error::ParseFailure;
 use crate::item::Item;
 use std::marker::PhantomData;
 #[doc(hidden)]
@@ -429,15 +432,18 @@ pub mod parsers {
     //! In most cases you won't be using those names directly, they're only listed here to provide
     //! access to documentation
     #[cfg(feature = "autocomplete")]
+    #[doc(inline)]
     pub use crate::complete_shell::ParseCompShell;
-    pub use crate::params::{NamedArg, ParseArgument, ParseCommand, ParsePositional};
+    #[doc(inline)]
+    pub use crate::params::{NamedArg, ParseAny, ParseArgument, ParseCommand, ParsePositional};
+    #[doc(inline)]
     pub use crate::structs::{ParseBox, ParseFallback, ParseMany, ParseOptional, ParseSome};
 }
 
 use structs::{
-    ParseAdjacent, ParseAnywhere, ParseFail, ParseFallback, ParseFallbackWith, ParseGroupHelp,
-    ParseGuard, ParseHide, ParseHideUsage, ParseMany, ParseMap, ParseOptional, ParseOrElse,
-    ParsePure, ParsePureWith, ParseSome, ParseWith,
+    ParseFail, ParseFallback, ParseFallbackWith, ParseGroupHelp, ParseGuard, ParseHide,
+    ParseHideUsage, ParseMany, ParseMap, ParseOptional, ParseOrElse, ParsePure, ParsePureWith,
+    ParseSome, ParseWith,
 };
 
 #[cfg(feature = "autocomplete")]
@@ -450,7 +456,7 @@ pub use crate::info::OptionParser;
 pub use crate::meta::Meta;
 
 #[doc(inline)]
-pub use crate::params::{any, command, env, long, positional, short};
+pub use crate::params::{any, command, env, literal, long, positional, short};
 
 #[cfg(doc)]
 pub(self) use crate::parsers::NamedArg;
@@ -1342,91 +1348,6 @@ pub trait Parser<T> {
     }
     // }}}
 
-    // {{{ adjacent
-    /// Automagically restrict the inner parser scope to accept adjacent values only
-    ///
-    /// `adjacent` can solve surprisingly wide variety of problems: sequential command chaining,
-    /// multi-value arguments, option-structs to name a few. If you want to run a parser on a
-    /// sequential subset of arguments - `adjacent` might be able to help you. Check the examples
-    /// for better intuition.
-    ///
-    /// # Multi-value arguments
-    ///
-    /// Parsing things like `--foo ARG1 ARG2 ARG3`
-    #[doc = include_str!("docs/adjacent_0.md")]
-    ///
-    /// # Structure groups
-    ///
-    /// Parsing things like `--foo --foo-1 ARG1 --foo-2 ARG2 --foo-3 ARG3`
-    #[doc = include_str!("docs/adjacent_1.md")]
-    ///
-    /// # Chaining commands
-    ///
-    /// Parsing things like `cmd1 --arg1 cmd2 --arg2 --arg3 cmd3 --flag`
-    ///
-    #[doc = include_str!("docs/adjacent_2.md")]
-    ///
-    /// # Start and end markers
-    ///
-    /// Parsing things like `find . --exec foo {} -bar ; --more`
-    ///
-    #[doc = include_str!("docs/adjacent_3.md")]
-    ///
-    /// # Multi-value arguments with optional flags
-    ///
-    /// Parsing things like `--foo ARG1 --flag --inner ARG2`
-    ///
-    /// So you can parse things while parsing things. Not sure why you might need this, but you can
-    /// :)
-    ///
-    #[doc = include_str!("docs/adjacent_4.md")]
-    ///
-    /// # Performance and other considerations
-    ///
-    /// `bpaf` can run adjacently restricted parsers multiple times to refine the guesses. It's
-    /// best not to have complex inter-fields verification since they might trip up the detection
-    /// logic: instead of destricting, for example "sum of two fields to be 5 or greater" *inside* the
-    /// `adjacent` parser, you can restrict it *outside*, once `adjacent` done the parsing.
-    ///
-    /// `adjacent` is available on a trait for better discoverability, it doesn't make much sense to
-    /// use it on something other than [`command`](OptionParser::command) or [`construct!`] encasing
-    /// several fields.
-    ///
-    /// There's also similar method [`adjacent`](crate::parsers::ParseArgument) that allows to restrict argument
-    /// parser to work only for arguments where both key and a value are in the same shell word:
-    /// `-f=bar` or `-fbar`, but not `-f bar`.
-    #[must_use]
-    fn adjacent(self) -> ParseAdjacent<Self>
-    where
-        Self: Sized + Parser<T>,
-    {
-        ParseAdjacent { inner: self }
-    }
-    // }}}
-
-    /// Parse anywhere
-    ///
-    /// Most generic escape hatch available, in combination with [`any`] allows to parse anything
-    /// anywhere, works by repeatedly trying to run the inner parser on each subsequent context.
-    /// Can be expensive performance wise especially if parser contains complex logic.
-    ///
-    #[doc = include_str!("docs/anywhere.md")]
-    ///
-    /// When using parsers annotated with `anywhere` it's a good idea to place them before other
-    /// parsers so combinations they are looking for are not consumed by simplier parsers.
-    ///
-    #[doc = include_str!("docs/anywhere_1.md")]
-    #[must_use]
-    fn anywhere(self) -> ParseAnywhere<Self>
-    where
-        Self: Sized + Parser<T>,
-    {
-        ParseAnywhere {
-            inner: self,
-            catch: false,
-        }
-    }
-
     // consume
     // {{{ to_options
     /// Transform `Parser` into [`OptionParser`] to attach metadata and run
@@ -1633,69 +1554,6 @@ pub fn fail<T>(msg: &'static str) -> ParseFail<T> {
     }
 }
 
-/// Unsuccessful command line parsing outcome, use it for unit tests
-///
-/// Useful for unit testing for user parsers, consume it with
-/// [`ParseFailure::unwrap_stdout`] and [`ParseFailure::unwrap_stdout`]
-#[derive(Clone, Debug)]
-pub enum ParseFailure {
-    /// Print this to stdout and exit with success code
-    Stdout(String),
-    /// Print this to stderr and exit with failure code
-    Stderr(String),
-}
-
-impl ParseFailure {
-    /// Returns the contained `stderr` values - for unit tests
-    ///
-    /// # Panics
-    ///
-    /// Panics if failure contains `stdout`
-    #[allow(clippy::must_use_candidate)]
-    #[track_caller]
-    pub fn unwrap_stderr(self) -> String {
-        match self {
-            Self::Stderr(err) => err,
-            Self::Stdout(_) => {
-                panic!("not an stderr: {:?}", self)
-            }
-        }
-    }
-
-    /// Returns the contained `stdout` values - for unit tests
-    ///
-    /// # Panics
-    ///
-    /// Panics if failure contains `stderr`
-    #[allow(clippy::must_use_candidate)]
-    #[track_caller]
-    pub fn unwrap_stdout(self) -> String {
-        match self {
-            Self::Stdout(err) => err,
-            Self::Stderr(_) => {
-                panic!("not an stdout: {:?}", self)
-            }
-        }
-    }
-
-    /// Run an action appropriate to the failure and produce the exit code
-    ///
-    /// Prints a message to `stdout` or `stderr` and returns the exit code
-    #[allow(clippy::must_use_candidate)]
-    pub fn exit_code(self) -> i32 {
-        match self {
-            ParseFailure::Stdout(msg) => {
-                print!("{}", msg); // completions are sad otherwise
-                0
-            }
-            ParseFailure::Stderr(msg) => {
-                eprintln!("{}", msg);
-                1
-            }
-        }
-    }
-}
-
 /// Strip a command name if present at the front when used as a `cargo` command
 ///
 /// See batteries::cargo_helper
diff --git a/src/manpage.rs b/src/manpage.rs
index 0df5762..1143b80 100644
--- a/src/manpage.rs
+++ b/src/manpage.rs
@@ -179,6 +179,7 @@ impl Line<'_> {
         self.line.push(bold(s));
         self
     }
+    #[allow(dead_code)]
     fn italic<S: Into<String>>(&mut self, s: S) -> &mut Self {
         self.line.push(italic(s));
         self
@@ -194,6 +195,7 @@ impl Line<'_> {
                 metavar,
                 strict: _,
                 help: _,
+                anywhere: _,
             } => {
                 self.metavar(metavar.0);
             }
@@ -217,20 +219,6 @@ impl Line<'_> {
             } => {
                 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
     }
@@ -261,7 +249,7 @@ impl Line<'_> {
             Meta::Many(m) => {
                 self.usage(m).norm("...");
             }
-            Meta::Anywhere(m) => {
+            Meta::Adjacent(m) => {
                 self.usage(m);
             }
             Meta::Item(item) => {
@@ -279,21 +267,21 @@ impl Line<'_> {
     }
 }
 
-fn flatten_commands<'a>(item: &HelpItem<'a>, path: &str, acc: &mut Vec<(String, HelpItem<'a>)>) {
+fn flatten_commands<'a>(item: HelpItem<'a>, path: &str, acc: &mut Vec<(String, HelpItem<'a>)>) {
     match item {
         HelpItem::Command { name, meta, .. } => {
-            acc.push((path.to_string(), *item));
+            acc.push((path.to_string(), item));
             let mut hi = HelpItems::default();
-            hi.classify(meta);
-            if !hi.cmds.is_empty() {
+            hi.append_meta(meta);
+            if hi.cmds().next().is_some() {
                 let path = format!("{} {}", path, name);
-                for help_item in &hi.cmds {
-                    flatten_commands(help_item, &path, acc);
+                for help_item in hi.cmds() {
+                    flatten_commands(help_item.clone(), &path, acc);
                 }
             }
         }
-        HelpItem::Decor { .. } => {
-            acc.push((String::new(), *item));
+        HelpItem::DecorHeader { .. } => {
+            acc.push((String::new(), item));
         }
         _ => {}
     }
@@ -327,23 +315,23 @@ fn command_help(manpage: &mut Manpage, item: &HelpItem, path: &str) {
                 }
             }
             let mut hi = HelpItems::default();
-            hi.classify(meta);
+            hi.append_meta(meta);
 
-            if !hi.psns.is_empty() {
+            if hi.psns().next().is_some() {
                 manpage.subsection("Positional items");
-                for item in &hi.psns {
-                    help_item(manpage, *item, None);
+                for item in hi.psns().cloned() {
+                    help_item(manpage, item, None);
                 }
             }
 
-            if !hi.flgs.is_empty() {
+            if hi.flgs().next().is_some() {
                 manpage.subsection("Option arguments and flags");
-                for item in &hi.flgs {
-                    help_item(manpage, *item, None);
+                for item in hi.flgs().cloned() {
+                    help_item(manpage, item, None);
                 }
             }
         }
-        HelpItem::Decor { help, margin: _ } => {
+        HelpItem::DecorHeader { help, ty: _ } => {
             manpage.subsection(*help);
         }
         _ => {}
@@ -352,13 +340,17 @@ fn command_help(manpage: &mut Manpage, item: &HelpItem, path: &str) {
 
 fn help_item(manpage: &mut Manpage, item: HelpItem, command_path: Option<&str>) {
     match item {
-        HelpItem::Decor { help, margin: _ } => {
+        HelpItem::DecorHeader { help, ty: _ } => {
             manpage.subsection(help);
         }
-        HelpItem::BlankDecor => {
+        HelpItem::DecorBlank { .. } => {
             manpage.text([]);
         }
-        HelpItem::Positional { metavar, help } => {
+        HelpItem::Positional {
+            metavar,
+            help,
+            anywhere: _,
+        } => {
             manpage.label(|l| {
                 l.metavar(metavar.0);
             });
@@ -407,21 +399,9 @@ fn help_item(manpage: &mut Manpage, item: HelpItem, command_path: Option<&str>)
                 manpage.text([norm(help)]);
             }
         }
-        HelpItem::MultiArg { name, help, fields } => {
-            manpage.label(|l| {
-                l.shortlong(name, true).norm("=");
-                for (ix, (m, _help)) in fields.iter().enumerate() {
-                    if ix > 0 {
-                        l.norm(" ");
-                    }
-                    l.metavar(m.0);
-                }
-            });
-
-            if let Some(help) = help {
-                manpage.text([norm(help)]);
-            }
-        }
+        HelpItem::AnywhereStart { .. }
+        | HelpItem::AnywhereStop { .. }
+        | HelpItem::DecorSuffix { .. } => {}
     }
 }
 
@@ -478,7 +458,7 @@ impl<T> OptionParser<T> {
     ) -> String {
         let mut hi = HelpItems::default();
         let meta = self.inner.meta();
-        hi.classify(&meta);
+        hi.append_meta(&meta);
 
         let mut manpage = Manpage::new(app, section, Some(date), None, None);
 
@@ -508,28 +488,28 @@ impl<T> OptionParser<T> {
         }
 
         // --------------------------------------------------------------
-        if !hi.psns.is_empty() {
+        if hi.psns().next().is_some() {
             manpage.subsection("Positional items");
-            for item in &hi.psns {
-                help_item(&mut manpage, *item, None);
+            for item in hi.psns().cloned() {
+                help_item(&mut manpage, item, None);
             }
         }
 
-        if !hi.flgs.is_empty() {
+        if hi.flgs().next().is_some() {
             manpage.subsection("Option arguments and flags");
-            for item in &hi.flgs {
-                help_item(&mut manpage, *item, None);
+            for item in hi.flgs().cloned() {
+                help_item(&mut manpage, item, None);
             }
         }
 
-        if !hi.cmds.is_empty() {
+        if hi.cmds().next().is_some() {
             manpage.subsection("List of all the subcommands");
             let mut commands = Vec::new();
-            for item in &hi.cmds {
+            for item in hi.cmds().cloned() {
                 flatten_commands(item, app, &mut commands);
             }
             for (path, item) in &commands {
-                help_item(&mut manpage, *item, Some(path));
+                help_item(&mut manpage, item.clone(), Some(path));
             }
             manpage.section("SUBCOMMANDS WITH OPTIONS");
 
diff --git a/src/meta.rs b/src/meta.rs
index 7590e5d..bc19c43 100644
--- a/src/meta.rs
+++ b/src/meta.rs
@@ -3,7 +3,9 @@ use crate::item::Item;
 #[doc(hidden)]
 #[derive(Copy, Clone, Debug)]
 pub enum DecorPlace {
+    /// Text is placed 2 spaces after the start of the line
     Header,
+    /// Text is placed after the tabstop
     Suffix,
 }
 #[doc(hidden)]
@@ -13,7 +15,7 @@ pub enum Meta {
     Or(Vec<Meta>),
     Optional(Box<Meta>),
     Required(Box<Meta>),
-    Anywhere(Box<Meta>),
+    Adjacent(Box<Meta>),
     Item(Box<Item>),
     Many(Box<Meta>),
     Decorated(Box<Meta>, String, DecorPlace),
@@ -30,6 +32,11 @@ impl Default for Meta {
 impl std::fmt::Display for Meta {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         fn go(meta: &Meta, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            // this macro does nothing other than
+            // stopping you from using Display impl of Meta since it would lose alternative state
+            #[allow(unused_macros)]
+            macro_rules! write {{} => {}}
+
             match meta {
                 Meta::And(xs) => {
                     for (ix, x) in xs.iter().enumerate() {
@@ -66,8 +73,8 @@ impl std::fmt::Display for Meta {
                 }
 
                 // hmm... Do I want to use special syntax here?
-                Meta::Anywhere(m) => m.fmt(f),
-                Meta::Decorated(m, _, _) => m.fmt(f),
+                Meta::Adjacent(m) => go(m, f),
+                Meta::Decorated(m, _, _) => go(m, f),
                 Meta::Skip => f.write_str("no parameters expected"),
                 Meta::HideUsage(m) => {
                     if !f.alternate() {
@@ -134,7 +141,7 @@ impl Meta {
                         go(meta, &mut command_pos, v);
                     }
                 }
-                Meta::Anywhere(m) => {
+                Meta::Adjacent(m) => {
                     let mut inner = false;
                     go(m, &mut inner, v);
                 }
@@ -236,7 +243,7 @@ impl Meta {
                 m.normalize(for_usage);
                 *self = std::mem::take(m);
             }
-            Meta::Anywhere(m) => {
+            Meta::Adjacent(m) => {
                 m.normalize(for_usage);
                 if matches!(**m, Meta::Skip) {
                     *self = Meta::Skip;
@@ -292,14 +299,12 @@ impl Meta {
             Meta::Item(m) => match &**m {
                 Item::Positional { .. } | Item::Command { .. } => {}
                 Item::Flag { shorts, .. } => flags.extend(shorts),
-                Item::MultiArg { shorts, .. } | Item::Argument { shorts, .. } => {
-                    args.extend(shorts);
-                }
+                Item::Argument { shorts, .. } => args.extend(shorts),
             },
             Meta::HideUsage(m)
             | Meta::Required(m)
             | Meta::Optional(m)
-            | Meta::Anywhere(m)
+            | Meta::Adjacent(m)
             | Meta::Many(m)
             | Meta::Decorated(m, _, _) => {
                 m.collect_shorts(flags, args);
diff --git a/src/meta_help.rs b/src/meta_help.rs
index 2b5025e..9b05121 100644
--- a/src/meta_help.rs
+++ b/src/meta_help.rs
@@ -16,7 +16,7 @@ pub struct Metavar(pub(crate) &'static str);
 
 impl Metavar {
     // don't render <> around the metavar if
-    fn custom(&self) -> bool {
+    fn is_custom(&self) -> bool {
         self.0
             .as_bytes()
             .first()
@@ -27,7 +27,7 @@ impl Metavar {
 impl std::fmt::Display for Metavar {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         use std::fmt::Write;
-        let hide_triangles = f.alternate() || self.custom();
+        let hide_triangles = f.alternate() || self.is_custom();
         if !hide_triangles {
             f.write_char('<')?;
         }
@@ -39,14 +39,21 @@ impl std::fmt::Display for Metavar {
     }
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
 pub(crate) enum HelpItem<'a> {
-    Decor {
+    DecorSuffix {
         help: &'a str,
-        margin: DecorPlace,
+        ty: HiTy,
+    },
+    DecorHeader {
+        help: &'a str,
+        ty: HiTy,
+    },
+    DecorBlank {
+        ty: HiTy,
     },
-    BlankDecor,
     Positional {
+        anywhere: bool,
         metavar: Metavar,
         help: Option<&'a str>,
     },
@@ -70,31 +77,52 @@ pub(crate) enum HelpItem<'a> {
         env: Option<&'static str>,
         help: Option<&'a str>,
     },
-    MultiArg {
-        name: ShortLong,
-        help: Option<&'a str>,
-        fields: &'a [(Metavar, Option<String>)],
+    AnywhereStart {
+        inner: &'a Meta,
+        ty: HiTy,
+    },
+    AnywhereStop {
+        ty: HiTy,
     },
 }
+impl HelpItem<'_> {
+    fn has_help(&self) -> bool {
+        match self {
+            HelpItem::Positional { help, .. }
+            | HelpItem::Command { help, .. }
+            | HelpItem::Flag { help, .. }
+            | HelpItem::Argument { help, .. } => help.is_some(),
+            HelpItem::DecorHeader { .. } | HelpItem::DecorSuffix { .. } => true,
+            HelpItem::DecorBlank { .. }
+            | HelpItem::AnywhereStart { .. }
+            | HelpItem::AnywhereStop { .. } => false,
+        }
+    }
+
+    fn ty(&self) -> HiTy {
+        match self {
+            HelpItem::DecorHeader { ty, .. }
+            | HelpItem::DecorSuffix { ty, .. }
+            | HelpItem::DecorBlank { ty }
+            | HelpItem::AnywhereStart { ty, .. }
+            | HelpItem::AnywhereStop { ty } => *ty,
+            HelpItem::Positional {
+                anywhere: false, ..
+            } => HiTy::Positional,
+            HelpItem::Command { .. } => HiTy::Command,
+            HelpItem::Positional { anywhere: true, .. }
+            | HelpItem::Flag { .. }
+            | HelpItem::Argument { .. } => HiTy::Flag,
+        }
+    }
+}
 
 #[derive(Default, Debug)]
 /// A collection of all the help items separated into flags, positionals and commands
 ///
 /// Items are stored as references and can be trivially copied
 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<'_> {
-    fn is_decor(&self) -> bool {
-        match self {
-            HelpItem::Decor { .. } | HelpItem::BlankDecor => true,
-            _ => false,
-        }
-    }
+    items: Vec<HelpItem<'a>>,
 }
 
 fn dedup(items: &mut BTreeSet<String>, buf: &mut Buffer, cp: Checkpoint) {
@@ -104,162 +132,185 @@ fn dedup(items: &mut BTreeSet<String>, buf: &mut Buffer, cp: Checkpoint) {
     }
 }
 
-impl<'a> HelpItems<'a> {
-    #[inline(never)]
-    /// Store a reference to this item into corresponding class - flag, positional or command
-    pub(crate) fn classify_item(&mut self, item: &'a Item) {
-        match item {
-            Item::Positional {
-                metavar,
-                help,
-                strict: _,
-            } => {
-                if help.is_some() {
-                    let hi = HelpItem::Positional {
-                        metavar: *metavar,
-                        help: help.as_deref(),
-                    };
-                    if self.anywhere {
-                        self.flgs.push(hi);
-                    } else {
-                        self.psns.push(hi);
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+
+pub(crate) enum HiTy {
+    Flag,
+    Command,
+    Positional,
+}
+
+enum Block {
+    No,
+    Decor(HiTy),
+    Anywhere(HiTy),
+}
+
+pub(crate) struct HelpItemsIter<'a, 'b> {
+    items: &'b [HelpItem<'a>],
+    target: HiTy,
+    cur: usize,
+    block: Block,
+}
+
+impl<'a, 'b> Iterator for HelpItemsIter<'a, 'b> {
+    type Item = &'b HelpItem<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            let item = self.items.get(self.cur)?;
+            self.cur += 1;
+
+            let keep = match item {
+                HelpItem::AnywhereStart { ty, .. } => {
+                    self.block = Block::Anywhere(*ty);
+                    *ty == self.target
+                }
+                HelpItem::DecorHeader { ty, .. } => {
+                    self.block = Block::Decor(*ty);
+                    *ty == self.target
+                }
+                HelpItem::DecorBlank { ty, .. } | HelpItem::AnywhereStop { ty, .. } => {
+                    self.block = Block::No;
+                    *ty == self.target
+                }
+                HelpItem::DecorSuffix { .. }
+                | HelpItem::Command { .. }
+                | HelpItem::Positional { .. }
+                | HelpItem::Flag { .. }
+                | HelpItem::Argument { .. } => {
+                    let ty = item.ty();
+                    match self.block {
+                        Block::No => ty == self.target,
+                        Block::Decor(t) => t == self.target,
+                        Block::Anywhere(t) => t == self.target && item.has_help(),
                     }
                 }
+            };
+            if keep {
+                return Some(item);
             }
-            Item::Command {
-                name,
-                short,
-                help,
+        }
+    }
+}
 
-                #[cfg(not(feature = "manpage"))]
-                    meta: _,
-                #[cfg(not(feature = "manpage"))]
-                    info: _,
-                #[cfg(feature = "manpage")]
-                info,
-                #[cfg(feature = "manpage")]
-                meta,
-            } => {
-                let hi = HelpItem::Command {
-                    name,
-                    #[cfg(feature = "manpage")]
-                    info,
-                    #[cfg(feature = "manpage")]
-                    meta,
-                    short: *short,
-                    help: help.as_deref(),
-                };
-                if self.anywhere {
-                    self.flgs.push(hi);
-                } else {
-                    self.cmds.push(hi);
-                }
+impl<'a> HelpItems<'a> {
+    pub(crate) fn flgs(&'_ self) -> impl Iterator<Item = &'_ HelpItem<'a>> + '_ {
+        HelpItemsIter {
+            items: &self.items,
+            target: HiTy::Flag,
+            cur: 0,
+            block: Block::No,
+        }
+    }
+
+    pub(crate) fn cmds(&'_ self) -> impl Iterator<Item = &'_ HelpItem<'a>> + '_ {
+        HelpItemsIter {
+            items: &self.items,
+            target: HiTy::Command,
+            cur: 0,
+            block: Block::No,
+        }
+    }
+
+    pub(crate) fn psns(&'_ self) -> impl Iterator<Item = &'_ HelpItem<'a>> + '_ {
+        HelpItemsIter {
+            items: &self.items,
+            target: HiTy::Positional,
+            cur: 0,
+            block: Block::No,
+        }
+    }
+
+    fn write_items(&self, target: HiTy) -> String {
+        let mut buf = Buffer::default();
+        let mut dedup_cache: BTreeSet<String> = BTreeSet::new();
+
+        // item should be written if
+        // - it is outside of the decorated block and item type matches
+        // - it is inside of the decorated block and block type matches
+        // - inside of anywhere block, block type matches and there's help
+        //
+        // BlankDecor and AnywhereStop end current decorated block.
+        for item in (HelpItemsIter {
+            items: &self.items,
+            target,
+            cur: 0,
+            block: Block::No,
+        }) {
+            let cp = buf.checkpoint();
+            write_help_item(&mut buf, item);
+            if matches!(
+                item,
+                HelpItem::Command { .. }
+                    | HelpItem::Positional { .. }
+                    | HelpItem::Flag { .. }
+                    | HelpItem::Argument { .. }
+            ) {
+                dedup(&mut dedup_cache, &mut buf, cp);
             }
-            Item::Flag {
-                name,
-                help,
-                env,
-                shorts: _,
-            } => self.flgs.push(HelpItem::Flag {
-                name: *name,
-                env: *env,
-                help: help.as_deref(),
-            }),
-            Item::Argument {
-                name,
-                metavar,
-                env,
-                help,
-                shorts: _,
-            } => self.flgs.push(HelpItem::Argument {
-                name: *name,
-                metavar: *metavar,
-                env: *env,
-                help: help.as_deref(),
-            }),
-            Item::MultiArg {
-                name,
-                shorts: _,
-                help,
-                fields,
-            } => self.flgs.push(HelpItem::MultiArg {
-                name: *name,
-                help: help.as_deref(),
-                fields,
-            }),
+        }
+        // return buffer to decoupe tabstop between blocks
+        buf.to_string()
+    }
+}
+
+impl Meta {
+    fn peek_front_ty(&self) -> Option<HiTy> {
+        match self {
+            Meta::And(xs) | Meta::Or(xs) => xs.iter().flat_map(|x| x.peek_front_ty()).next(),
+            Meta::Optional(x)
+            | Meta::Required(x)
+            | Meta::Adjacent(x)
+            | Meta::Many(x)
+            | Meta::Decorated(x, _, _)
+            | Meta::HideUsage(x) => x.peek_front_ty(),
+            Meta::Item(i) => Some(HiTy::from(i.as_ref())),
+            Meta::Skip => None,
         }
     }
+}
 
+impl<'a> HelpItems<'a> {
     /// Recursively classify contents of the Meta
-    pub(crate) fn classify(&mut self, meta: &'a Meta) {
+    pub(crate) fn append_meta(&mut self, meta: &'a Meta) {
         match meta {
             Meta::And(xs) | Meta::Or(xs) => {
                 for x in xs {
-                    self.classify(x);
+                    self.append_meta(x);
                 }
             }
-            Meta::Anywhere(x) => {
-                let prev_anywhere = self.anywhere;
-                self.anywhere = true;
-                self.classify(x);
-                self.anywhere = prev_anywhere;
+            Meta::Adjacent(m) => {
+                if let Some(ty) = m.peek_front_ty() {
+                    self.items.push(HelpItem::AnywhereStart {
+                        inner: m.as_ref(),
+                        ty,
+                    });
+                    self.append_meta(m);
+                    self.items.push(HelpItem::AnywhereStop { ty });
+                }
             }
             Meta::HideUsage(x) | Meta::Required(x) | Meta::Optional(x) | Meta::Many(x) => {
-                self.classify(x)
+                self.append_meta(x)
             }
-            Meta::Item(item) => self.classify_item(item),
-
-            Meta::Decorated(m, help, DecorPlace::Header) => {
-                self.flgs.push(HelpItem::Decor {
-                    help,
-                    margin: DecorPlace::Header,
-                });
-                self.cmds.push(HelpItem::Decor {
-                    help,
-                    margin: DecorPlace::Header,
-                });
-                self.psns.push(HelpItem::Decor {
-                    help,
-                    margin: DecorPlace::Header,
-                });
-                self.classify(m);
-
-                if self.flgs.last().map_or(false, HelpItem::is_decor) {
-                    self.flgs.pop();
-                } else {
-                    self.flgs.push(HelpItem::BlankDecor);
-                }
-
-                if self.cmds.last().map_or(false, HelpItem::is_decor) {
-                    self.cmds.pop();
-                } else {
-                    self.cmds.push(HelpItem::BlankDecor);
+            Meta::Item(item) => {
+                if matches!(item.as_ref(), Item::Positional { help: None, .. }) {
+                    return;
                 }
-
-                if self.psns.last().map_or(false, HelpItem::is_decor) {
-                    self.psns.pop();
-                } else {
-                    self.psns.push(HelpItem::BlankDecor);
+                self.items.push(HelpItem::from(item.as_ref()));
+            }
+            Meta::Decorated(m, help, DecorPlace::Header) => {
+                if let Some(ty) = m.peek_front_ty() {
+                    self.items.push(HelpItem::DecorHeader { help, ty });
+                    self.append_meta(m);
+                    self.items.push(HelpItem::DecorBlank { ty });
                 }
             }
             Meta::Decorated(m, help, DecorPlace::Suffix) => {
-                let flgs = self.flgs.len();
-                let cmds = self.cmds.len();
-                let psns = self.psns.len();
-                self.classify(m);
-                let xs = if flgs != self.flgs.len() {
-                    &mut self.flgs
-                } else if psns != self.psns.len() {
-                    &mut self.psns
-                } else if cmds != self.cmds.len() {
-                    &mut self.cmds
-                } else {
-                    return;
-                };
-                xs.push(HelpItem::Decor {
-                    help,
-                    margin: DecorPlace::Suffix,
-                });
+                if let Some(ty) = m.peek_front_ty() {
+                    self.append_meta(m);
+                    self.items.push(HelpItem::DecorSuffix { help, ty });
+                }
             }
             Meta::Skip => (),
         }
@@ -283,15 +334,32 @@ impl std::fmt::Display for Short {
     }
 }
 
-impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
-    fn from(item: &'a crate::item::Item) -> Self {
+impl From<&Item> for HiTy {
+    fn from(value: &Item) -> Self {
+        match value {
+            Item::Positional {
+                anywhere: false, ..
+            } => Self::Positional,
+            Item::Command { .. } => Self::Command,
+            Item::Positional { anywhere: true, .. } | Item::Flag { .. } | Item::Argument { .. } => {
+                Self::Flag
+            }
+        }
+    }
+}
+
+impl<'a> From<&'a Item> for HelpItem<'a> {
+    // {{{
+    fn from(item: &'a Item) -> Self {
         match item {
             Item::Positional {
                 metavar,
                 help,
                 strict: _,
+                anywhere,
             } => Self::Positional {
                 metavar: *metavar,
+                anywhere: *anywhere,
                 help: help.as_deref(),
             },
             Item::Command {
@@ -337,22 +405,12 @@ impl<'a> From<&'a crate::item::Item> for HelpItem<'a> {
                 env: *env,
                 help: help.as_deref(),
             },
-            Item::MultiArg {
-                name,
-                shorts: _,
-                help,
-                fields,
-            } => Self::MultiArg {
-                name: *name,
-                help: help.as_deref(),
-                fields,
-            },
         }
     }
-}
+} // }}}
 
 fn write_metavar(buf: &mut Buffer, metavar: Metavar) {
-    let custom = metavar.custom();
+    let custom = metavar.is_custom();
     if !custom {
         buf.write_char('<', Style::Label);
     }
@@ -364,19 +422,20 @@ fn write_metavar(buf: &mut Buffer, metavar: Metavar) {
 
 fn write_help_item(buf: &mut Buffer, item: &HelpItem) {
     match item {
-        HelpItem::Decor { help, margin } => {
-            match margin {
-                DecorPlace::Header => {
-                    buf.margin(2);
-                }
-                DecorPlace::Suffix => {
-                    buf.tabstop();
-                }
-            }
+        HelpItem::DecorHeader { help, .. } => {
+            buf.margin(2);
+            buf.write_str(help, Style::Text);
+        }
+        HelpItem::DecorSuffix { help, .. } => {
+            buf.tabstop();
             buf.write_str(help, Style::Text);
         }
-        HelpItem::BlankDecor => {}
-        HelpItem::Positional { metavar, help } => {
+        HelpItem::DecorBlank { .. } | HelpItem::AnywhereStop { .. } => {}
+        HelpItem::Positional {
+            metavar,
+            help,
+            anywhere: _,
+        } => {
             buf.margin(4);
             write_metavar(buf, *metavar);
             if let Some(help) = help {
@@ -451,29 +510,13 @@ fn write_help_item(buf: &mut Buffer, item: &HelpItem) {
                 buf.write_str(help, Style::Text);
             }
         }
-        HelpItem::MultiArg { name, help, fields } => {
-            buf.margin(4);
-            write_shortlong(buf, *name);
-            for (field, _) in fields.iter() {
-                buf.write_str(" ", Style::Label);
-                write_metavar(buf, *field);
-            }
-
-            if let Some(help) = help {
-                buf.tabstop();
-                buf.write_str(help, Style::Text);
-            }
-            buf.margin(12);
-            for (field, help) in fields.iter() {
-                if let Some(help) = help {
-                    buf.newline();
-                    write_metavar(buf, *field);
-                    buf.tabstop();
-                    buf.write_str(help, Style::Text);
-                }
-            }
-        }
+        HelpItem::AnywhereStart { inner, .. } => {
+            buf.margin(2);
+            let descr = format!("{:#}", inner);
+            buf.write_str(&descr, Style::Text);
+        } //            return;
     }
+
     buf.newline();
 }
 
@@ -504,25 +547,6 @@ fn write_as_lines(buf: &mut Buffer, line: &str) {
     }
 }
 
-fn write_items(items: &[HelpItem], descr: &str) -> String {
-    if items.is_empty() {
-        String::new()
-    } else {
-        let mut buf = Buffer::default();
-        let mut dedup_cache: BTreeSet<String> = BTreeSet::new();
-        buf.newline();
-        buf.margin(0);
-        buf.write_str(descr, Style::Section);
-        buf.newline();
-        for i in items {
-            let cp = buf.checkpoint();
-            write_help_item(&mut buf, i);
-            dedup(&mut dedup_cache, &mut buf, cp);
-        }
-        buf.to_string()
-    }
-}
-
 pub fn render_help(info: &Info, parser_meta: &Meta, help_meta: &Meta) -> String {
     let mut res = String::new();
     let mut buf = Buffer::default();
@@ -551,14 +575,40 @@ pub fn render_help(info: &Info, parser_meta: &Meta, help_meta: &Meta) -> String
     }
 
     let mut items = HelpItems::default();
-    items.classify(parser_meta);
-    items.classify(help_meta);
+    items.append_meta(parser_meta);
+    items.append_meta(help_meta);
 
+    fn write_section_title(buf: &mut Buffer, label: &str) {
+        buf.newline();
+        buf.margin(0);
+        buf.write_str(label, Style::Section);
+        buf.newline();
+    }
     res.push_str(&buf.to_string());
 
-    res.push_str(&write_items(&items.psns, "Available positional items:"));
-    res.push_str(&write_items(&items.flgs, "Available options:"));
-    res.push_str(&write_items(&items.cmds, "Available commands:"));
+    let section = items.write_items(HiTy::Positional);
+    if !section.is_empty() {
+        buf.clear();
+        write_section_title(&mut buf, "Available positional items:");
+        res.push_str(&buf.to_string());
+        res.push_str(section.as_str());
+    }
+
+    let section = items.write_items(HiTy::Flag);
+    if !section.is_empty() {
+        buf.clear();
+        write_section_title(&mut buf, "Available options:");
+        res.push_str(&buf.to_string());
+        res.push_str(section.as_str());
+    }
+
+    let section = items.write_items(HiTy::Command);
+    if !section.is_empty() {
+        buf.clear();
+        write_section_title(&mut buf, "Available commands:");
+        res.push_str(&buf.to_string());
+        res.push_str(section.as_str());
+    }
 
     let mut buf = Buffer::default();
     if let Some(footer) = info.footer {
diff --git a/src/meta_youmean.rs b/src/meta_youmean.rs
index 9200cae..415a968 100644
--- a/src/meta_youmean.rs
+++ b/src/meta_youmean.rs
@@ -15,7 +15,7 @@ pub(crate) fn suggest(args: &Args, meta: &Meta) -> Option<String> {
 
     variants.sort_by(|a, b| b.0.cmp(&a.0));
 
-    if let Some((l, (actual, expected))) = variants.pop() {
+    if let Some((l, (mut actual, expected))) = variants.pop() {
         if let (0, I::Nested(cmd)) = (l, expected) {
             let best = format!(
                 "{:?} is not valid in this context, did you mean to pass it to command \"{}\"?",
@@ -24,10 +24,16 @@ pub(crate) fn suggest(args: &Args, meta: &Meta) -> Option<String> {
             );
             return Some(best);
         } else if l > 0 && l < usize::MAX {
+            if let (I::LongCmd(act), I::LongFlag(expected)) = (actual, expected) {
+                if act.strip_prefix('-') == Some(expected) {
+                    actual = I::Ambiguity(act);
+                }
+            }
+
             let best = format!(
                 "No such {:?}, did you mean `{}`?",
                 actual,
-                w_flag!(expected)
+                w_flag!(expected),
             );
             return Some(best);
         }
@@ -94,7 +100,7 @@ fn inner_item<'a>(
         },
         Arg::Ambiguity(_, os) => {
             if let Some(s) = os.to_str() {
-                I::Ambiguity(s)
+                I::LongCmd(s)
             } else {
                 return;
             }
@@ -117,16 +123,14 @@ fn inner_item<'a>(
                 ins(I::ShortCmd(*s), actual, variants);
             }
         }
-        Item::Flag { name, .. } | Item::Argument { name, .. } | Item::MultiArg { name, .. } => {
-            match name {
-                ShortLong::Short(s) => ins(I::ShortFlag(*s), actual, variants),
-                ShortLong::Long(l) => ins(I::LongFlag(l), actual, variants),
-                ShortLong::ShortLong(s, l) => {
-                    ins(I::ShortFlag(*s), actual, variants);
-                    ins(I::LongFlag(l), actual, variants);
-                }
+        Item::Flag { name, .. } | Item::Argument { name, .. } => match name {
+            ShortLong::Short(s) => ins(I::ShortFlag(*s), actual, variants),
+            ShortLong::Long(l) => ins(I::LongFlag(l), actual, variants),
+            ShortLong::ShortLong(s, l) => {
+                ins(I::ShortFlag(*s), actual, variants);
+                ins(I::LongFlag(l), actual, variants);
             }
-        }
+        },
     }
 }
 
@@ -146,7 +150,7 @@ fn collect_suggestions<'a>(
         Meta::HideUsage(meta)
         | Meta::Optional(meta)
         | Meta::Required(meta)
-        | Meta::Anywhere(meta)
+        | Meta::Adjacent(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 08973e5..a8ee9e9 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -62,7 +62,8 @@ 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, meta_help::Metavar, Item, Meta,
+    args::Arg, error::MissingItem, 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
@@ -338,7 +339,7 @@ impl NamedArg {
     /// `skip` and `fallback` annotations, see examples.
     ///
     /// Additionally `bpaf_derive` handles `()` fields as `req_flag` see
-    /// [`adjacent`](Parser::adjacent) for more details.
+    /// [`adjacent`](crate::ParseCon::adjacent) for more details.
     /// See [`NamedArg`] for more details
     #[doc = include_str!("docs/req_flag.md")]
     #[must_use]
@@ -467,6 +468,7 @@ where
         shorts: Vec::new(),
         help: subparser.short_descr().map(Into::into),
         subparser,
+        adjacent: false,
     }
 }
 
@@ -478,6 +480,7 @@ pub struct ParseCommand<T> {
     shorts: Vec<char>,
     help: Option<String>,
     subparser: OptionParser<T>,
+    adjacent: bool,
 }
 
 impl<P> ParseCommand<P> {
@@ -554,6 +557,12 @@ impl<P> ParseCommand<P> {
         self.longs.push(long);
         self
     }
+
+    /// Allow for the command to suceed
+    pub fn adjacent(mut self) -> Self {
+        self.adjacent = true;
+        self
+    }
 }
 
 impl<T> Parser<T> for ParseCommand<T> {
@@ -576,20 +585,48 @@ impl<T> Parser<T> for ParseCommand<T> {
                 return Err(Error::Missing(Vec::new()));
             }
 
+            if let Some(cur) = args.current {
+                args.set_scope(cur..args.scope().end);
+            }
+
             args.head = usize::MAX;
             args.depth += 1;
-            // `or_else` would prefer failures past this point to preceeding levels
-            #[allow(clippy::let_and_return)]
-            let res = self
-                .subparser
-                .run_subparser(args)
-                .map_err(Error::ParseFailure);
-            res
+            if !self.adjacent {
+                self.subparser
+                    .run_subparser(args)
+                    .map_err(Error::ParseFailure)
+            } else {
+                let mut orig_args = args.clone();
+                match self
+                    .subparser
+                    .run_subparser(args)
+                    .map_err(Error::ParseFailure)
+                {
+                    Ok(ok) => Ok(ok),
+                    Err(err) => {
+                        let orig_scope = args.scope();
+                        if let Some(narrow_scope) = args.adjacent_scope(&orig_args) {
+                            orig_args.set_scope(narrow_scope);
+                            if let Ok(res) = self.subparser.run_subparser(&mut orig_args) {
+                                orig_args.set_scope(orig_scope);
+                                std::mem::swap(&mut orig_args, args);
+                                return Ok(res);
+                            }
+                        }
+                        Err(err)
+                    }
+                }
+            }
         } else {
             #[cfg(feature = "autocomplete")]
             args.push_command(self.longs[0], self.shorts.first().copied(), &self.help);
 
-            Err(Error::Missing(vec![self.item()]))
+            let missing = MissingItem {
+                item: self.item(),
+                position: args.scope().start,
+                scope: args.scope(),
+            };
+            Err(Error::Missing(vec![missing]))
         }
     }
 
@@ -642,7 +679,14 @@ impl<T: Clone + 'static> Parser<T> for ParseFlag<T> {
             args.push_flag(&self.named);
             match &self.absent {
                 Some(ok) => Ok(ok.clone()),
-                None => Err(Error::Missing(vec![self.named.flag_item()])),
+                None => {
+                    let missing = MissingItem {
+                        item: self.named.flag_item(),
+                        position: args.scope().start,
+                        scope: args.scope(),
+                    };
+                    Err(Error::Missing(vec![missing]))
+                }
             }
         }
     }
@@ -674,7 +718,7 @@ impl<T> ParseArgument<T> {
     /// Restrict parsed arguments to have both flag and a value in the same word:
     ///
     /// In other words adjacent restricted `ParseArgument` would accept `--flag=value` or
-    /// `-fbar` but not `--flag value`. Note, this is different from [`adjacent`](Parser::adjacent),
+    /// `-fbar` but not `--flag value`. Note, this is different from [`adjacent`](crate::ParseCon::adjacent),
     /// just plays a similar role.
     ///
     /// Should allow to parse some of the more unusual things
@@ -723,7 +767,12 @@ impl<T> ParseArgument<T> {
                     args.current = None;
                     Ok(val)
                 } else {
-                    Err(Error::Missing(vec![self.item()]))
+                    let missing = MissingItem {
+                        item: self.item(),
+                        position: args.scope().start,
+                        scope: args.scope(),
+                    };
+                    Err(Error::Missing(vec![missing]))
                 }
             }
         }
@@ -867,6 +916,7 @@ impl<T> ParsePositional<T> {
             metavar: Metavar(self.metavar),
             help: self.help.clone(),
             strict: self.strict,
+            anywhere: false,
         })
     }
 }
@@ -900,12 +950,18 @@ fn parse_word(
             args.no_pos_ahead = true;
         }
 
-        let item = Item::Positional {
-            metavar: Metavar(metavar),
-            help: help.clone(),
-            strict,
+        let position = args.items_iter().next().map_or(args.scope().end, |x| x.0);
+        let missing = MissingItem {
+            item: Item::Positional {
+                metavar: Metavar(metavar),
+                help: help.clone(),
+                strict,
+                anywhere: false,
+            },
+            position,
+            scope: args.scope(),
         };
-        Err(Error::Missing(vec![item]))
+        Err(Error::Missing(vec![missing]))
     }
 }
 
@@ -927,23 +983,24 @@ where
     }
 }
 
-/// Parse the next available item on a command line with no restrictions, created with [`any`].
-pub struct ParseAny<T> {
-    ty: PhantomData<T>,
+/// Consume an arbitrary value that satisfies a condition, created with [`any`], implements
+/// [`anywhere`](ParseAny::anywhere).
+pub struct ParseAny<T, I, F> {
     metavar: Metavar,
-    strict: bool,
     help: Option<String>,
+    ctx: PhantomData<(I, T)>,
+    check: F,
+    anywhere: bool,
 }
 
-/// Take next unconsumed item on the command line as raw [`String`] or [`OsString`]
+/// Take an arbitrary unconsumed item on the command line as parsed with [`FromStr`] or raw [`String`]/[`OsString`] value.
 ///
 /// **`any` is designed to consume items that don't fit into usual `flag`/`switch`/`positional`
 /// /`argument`/`command` classification**
 ///
-/// `any` behaves similar to [`positional`] so you should be using it near the right most end of
-/// the consumer struct. Note, consuming "anything" also consumes `--help` unless restricted
-/// with `guard`. It's better stick to `positional` unless you are trying to consume raw options
-/// to pass to some other process or do some special handling.
+/// By default `any` behaves similar to [`positional`] so you should be using it near the right
+/// most end of the consumer struct. It is possible to lift this restriction by calling
+/// [`anywhere`](ParseAny::anywhere) on the parser.
 ///
 /// When using combinatoring API you can specify the type with turbofish, for parsing types
 /// that don't implement [`FromStr`] you can use consume a `String`/`OsString` first and parse
@@ -952,89 +1009,108 @@ pub struct ParseAny<T> {
 /// # use bpaf::*;
 /// # use std::ffi::OsString;
 /// fn parse_any() -> impl Parser<OsString> {
-///     any::<OsString>("ANYTHING")
+///     // anything at all, including --help - in practice you want some restriction...
+///     any::<OsString, _, _>("ANYTHING", Some)
 /// }
 /// ```
 ///
 #[doc = include_str!("docs/any.md")]
 ///
-/// See [`adjacent`](Parser::adjacent) for more examples
+/// See [`adjacent`](crate::ParseCon::adjacent) for more examples
+/// See [`literal`] for a specialized version of `any` that consumes a fixed literal.
 #[must_use]
-pub fn any<T>(metavar: &'static str) -> ParseAny<T> {
+pub fn any<I, T, F>(metavar: &'static str, check: F) -> ParseAny<T, I, F> {
     ParseAny {
-        ty: PhantomData,
         metavar: Metavar(metavar),
-        strict: false,
         help: None,
+        check,
+        ctx: PhantomData,
+        anywhere: false,
     }
 }
 
-impl<T> ParseAny<T> {
-    /// Add a help message to [`any`] parser.
-    #[doc = include_str!("docs/any.md")]
-    #[must_use]
-    pub fn help<M: Into<String>>(mut self, help: M) -> Self {
-        self.help = Some(help.into());
-        self
-    }
-
-    fn meta(&self) -> Meta {
-        Meta::from(self.item())
-    }
+/// A specialized version of [`any`] that consumes an arbitrary string
+///
+/// ```no_run
+/// # use bpaf::*;
+/// /// Returns `true` if flag "-mode" is present and `false` otherwise.
+/// fn parse_mode() -> impl Parser<bool> {
+///     literal("-mode")
+///         .anywhere()
+///         .map(|_| true)
+///         .fallback(false)
+///
+/// }
+/// ```
+///
+/// See [`any`] for more examples
+pub fn literal(val: &'static str) -> ParseAny<(), String, impl Fn(String) -> Option<()>> {
+    any(val, move |s| if s == val { Some(()) } else { None })
+}
 
-    fn item(&self) -> Item {
+impl<F, I, T> ParseAny<T, F, I> {
+    pub(crate) fn item(&self) -> Item {
         Item::Positional {
             metavar: self.metavar,
-            strict: self.strict,
+            strict: false,
             help: self.help.clone(),
+            anywhere: self.anywhere,
         }
     }
 
-    /// returns real items only
-    fn next_os_string(&self, args: &mut Args) -> Result<OsString, Error> {
-        let (ix, item) = match args.items_iter().next() {
-            Some(item_with_index) => item_with_index,
-            None => return Err(Error::Missing(vec![self.item()])),
-        };
-        match item {
-            Arg::Ambiguity(_, s) => {
-                let os = s.clone();
-                args.remove(ix);
-                Ok(os)
-            }
-            Arg::Short(_, part, s) | Arg::Long(_, part, s) => {
-                let os = s.clone();
-                if *part {
-                    args.remove(ix + 1);
-                }
-                args.remove(ix);
-
-                Ok(os)
-            }
+    /// Add a help message to [`any`] parser.
+    #[doc = include_str!("docs/any.md")]
+    #[must_use]
+    pub fn help<M: Into<String>>(mut self, help: M) -> Self {
+        self.help = Some(help.into());
+        self
+    }
 
-            Arg::Word(w) | Arg::PosWord(w) => {
-                let os = w.clone();
-                args.remove(ix);
-                Ok(os)
-            }
-        }
+    /// Try to apply the parser to each unconsumed element instead of just the front one
+    ///
+    #[doc = include_str!("docs/anywhere.md")]
+    pub fn anywhere(mut self) -> Self {
+        self.anywhere = true;
+        self
     }
 }
 
-impl<T> Parser<T> for ParseAny<T>
+impl<F, I, T> Parser<T> for ParseAny<T, I, F>
 where
-    T: FromStr + 'static,
-    <T as std::str::FromStr>::Err: std::fmt::Display,
+    I: FromStr + 'static,
+    F: Fn(I) -> Option<T>,
+    <I as std::str::FromStr>::Err: std::fmt::Display,
 {
     fn eval(&self, args: &mut Args) -> Result<T, Error> {
-        let os = self.next_os_string(args)?;
-        match parse_os_str::<T>(os) {
-            Ok(ok) => Ok(ok),
-            Err(err) => Err(args.word_parse_error(&err)), // Error::Stderr(err)),
+        for (ix, x) in args.items_iter() {
+            let (os, next) = match x {
+                Arg::Ambiguity(_, _) => unreachable!("ambiguity was not resolved?"),
+                Arg::Short(_, next, os) | Arg::Long(_, next, os) => (os, *next),
+                Arg::Word(os) | Arg::PosWord(os) => (os, false),
+            };
+            if let Ok(i) = parse_os_str::<I>(os.clone()) {
+                if let Some(t) = (self.check)(i) {
+                    args.remove(ix);
+                    if next {
+                        args.remove(ix + 1);
+                    }
+
+                    return Ok(t);
+                }
+            }
+            if !self.anywhere {
+                break;
+            }
         }
+        let missing_item = MissingItem {
+            item: self.item(),
+            position: args.scope().start,
+            scope: args.scope(),
+        };
+        Err(Error::Missing(vec![missing_item]))
     }
 
     fn meta(&self) -> Meta {
-        self.meta()
+        Meta::Item(Box::new(self.item()))
     }
 }
diff --git a/src/structs.rs b/src/structs.rs
index 7f8c196..d5a7f43 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -1,5 +1,7 @@
 //! Structures that implement different methods on [`Parser`] trait
-use crate::{args::Conflict, info::Error, item::Item, meta::DecorPlace, Args, Meta, Parser};
+use crate::{
+    args::Conflict, error::MissingItem, item::Item, meta::DecorPlace, Args, Error, Meta, Parser,
+};
 use std::marker::PhantomData;
 
 #[cfg(feature = "autocomplete")]
@@ -518,6 +520,7 @@ impl<P> ParseMany<P> {
     }
 }
 
+/// try to parse
 fn parse_option<P, T>(parser: &P, args: &mut Args, catch: bool) -> Result<Option<T>, Error>
 where
     P: Parser<T>,
@@ -526,24 +529,43 @@ where
     match parser.eval(args) {
         Ok(val) => Ok(Some(val)),
         Err(err) => {
-            std::mem::swap(args, &mut orig_args);
-
-            #[cfg(feature = "autocomplete")]
-            if orig_args.comp_mut().is_some() {
-                args.swap_comps(&mut orig_args);
-            }
-
-            match err {
-                Error::Message(_, can_catch) if can_catch || catch => Ok(None),
-                Error::Message(_, _) | Error::ParseFailure(_) => Err(err),
+            // this is safe to return Ok(None) in following scenarios
+            // when inner parser never consumed anything and
+            // 1. produced Error::Missing
+            // 2. produced Error::Message(_, true)
+            // 3. produced Error::Message and catch is enabled
+            //
+            // In all other scenarios we should return the original error
+            //
+            // When parser returns Ok(None) we should return the original arguments so if there's
+            // anything left unconsumed - this won't be lost.
+
+            let res = match &err {
+                Error::Message(_, can_catch) => {
+                    if *can_catch || catch {
+                        Ok(None)
+                    } else {
+                        Err(err)
+                    }
+                }
+                Error::ParseFailure(_) => Err(err),
                 Error::Missing(_) => {
-                    if args.len() == orig_args.len() {
+                    if orig_args.len() == args.len() || catch {
                         Ok(None)
                     } else {
                         Err(err)
                     }
                 }
+            };
+            if res.is_ok() {
+                std::mem::swap(&mut orig_args, args);
+
+                #[cfg(feature = "autocomplete")]
+                if orig_args.comp_mut().is_some() {
+                    args.swap_comps(&mut orig_args);
+                }
             }
+            res
         }
     }
 }
@@ -667,6 +689,66 @@ where
     }
 }
 
+impl<T> ParseCon<T> {
+    #[must_use]
+
+    /// Automagically restrict the inner parser scope to accept adjacent values only
+    ///
+    /// `adjacent` can solve surprisingly wide variety of problems: sequential command chaining,
+    /// multi-value arguments, option-structs to name a few. If you want to run a parser on a
+    /// sequential subset of arguments - `adjacent` might be able to help you. Check the examples
+    /// for better intuition.
+    ///
+    /// # Multi-value arguments
+    ///
+    /// Parsing things like `--foo ARG1 ARG2 ARG3`
+    #[doc = include_str!("docs/adjacent_0.md")]
+    ///
+    /// # Structure groups
+    ///
+    /// Parsing things like `--foo --foo-1 ARG1 --foo-2 ARG2 --foo-3 ARG3`
+    #[doc = include_str!("docs/adjacent_1.md")]
+    ///
+    /// # Chaining commands
+    ///
+    /// Parsing things like `cmd1 --arg1 cmd2 --arg2 --arg3 cmd3 --flag`
+    ///
+    #[doc = include_str!("docs/adjacent_2.md")]
+    ///
+    /// # Start and end markers
+    ///
+    /// Parsing things like `find . --exec foo {} -bar ; --more`
+    ///
+    #[doc = include_str!("docs/adjacent_3.md")]
+    ///
+    /// # Multi-value arguments with optional flags
+    ///
+    /// Parsing things like `--foo ARG1 --flag --inner ARG2`
+    ///
+    /// So you can parse things while parsing things. Not sure why you might need this, but you can
+    /// :)
+    ///
+    #[doc = include_str!("docs/adjacent_4.md")]
+    ///
+    /// # Performance and other considerations
+    ///
+    /// `bpaf` can run adjacently restricted parsers multiple times to refine the guesses. It's
+    /// best not to have complex inter-fields verification since they might trip up the detection
+    /// logic: instead of destricting, for example "sum of two fields to be 5 or greater" *inside* the
+    /// `adjacent` parser, you can restrict it *outside*, once `adjacent` done the parsing.
+    ///
+    /// `adjacent` is available on a trait for better discoverability, it doesn't make much sense to
+    /// use it on something other than [`command`](OptionParser::command) or [`construct!`] encasing
+    /// several fields.
+    ///
+    /// There's also similar method [`adjacent`](crate::parsers::ParseArgument) that allows to restrict argument
+    /// parser to work only for arguments where both key and a value are in the same shell word:
+    /// `-f=bar` or `-fbar`, but not `-f bar`.
+    pub fn adjacent(self) -> ParseAdjacent<Self> {
+        ParseAdjacent { inner: self }
+    }
+}
+
 /// Parser that replaces metavar placeholders with actual info in shell completion
 #[cfg(feature = "autocomplete")]
 pub struct ParseComp<P, F> {
@@ -760,101 +842,20 @@ where
     P: Parser<T> + Sized,
 {
     fn eval(&self, args: &mut Args) -> Result<T, Error> {
-        let mut guess = 0..args.items.len();
-        let mut scratch = args.clone();
-        let mut res = self.inner.eval(&mut scratch);
-        let mut refined = true;
-        while refined {
-            refined = scratch.refine_range(args, &mut guess);
-            scratch = args.clone();
-            scratch.restrict_to_range(&guess);
-            res = self.inner.eval(&mut scratch);
-        }
-
-        if guess.start > 0 {
-            scratch.copy_usage_from(args, 0..guess.start);
-        }
-        if guess.end < args.items.len() {
-            scratch.copy_usage_from(args, guess.end..args.items.len());
-        }
-        std::mem::swap(args, &mut scratch);
-        res
-    }
-
-    fn meta(&self) -> Meta {
-        self.inner.meta()
-    }
-}
-
-/// Create boxed parser
-///
-/// Boxed parser doesn't expose internal representation in it's type and allows to return
-/// different parsers in different conditional branches
-///
-/// You can create it with a single argument `construct` macro or by using `boxed` annotation
-#[doc = include_str!("docs/boxed.md")]
-pub struct ParseBox<T> {
-    /// Boxed inner parser
-    pub inner: Box<dyn Parser<T>>,
-}
-
-impl<T> Parser<T> for ParseBox<T> {
-    fn eval(&self, args: &mut Args) -> Result<T, Error> {
-        self.inner.eval(args)
-    }
-    fn meta(&self) -> Meta {
-        self.inner.meta()
-    }
-}
-
-/// Parse anywhere
-///
-/// Most generic escape hatch available, in combination with [`any`] allows to parse anything
-/// anywhere, works by repeatedly trying to run the inner parser on each subsequent context.
-/// Can be expensive performance wise especially if parser contains complex logic.
-///
-#[doc = include_str!("docs/anywhere.md")]
-pub struct ParseAnywhere<P> {
-    pub(crate) inner: P,
-    pub(crate) catch: bool,
-}
-
-impl<P> ParseAnywhere<P> {
-    #[must_use]
-    /// Handle parse failures
-    ///
-    /// Can be useful to decide to skip parsing of some items on a command line
-    /// When parser succeeds - `catch` version would return a value as usual
-    /// if it fails - `catch` would restore all the consumed values and return None.
-    ///
-    /// There's several structures that implement this attribute: [`ParseOptional`], [`ParseMany`]
-    /// and [`ParseSome`], behavior should be identical for all of them.
-    #[doc = include_str!("docs/catch.md")]
-    pub fn catch(mut self) -> Self {
-        self.catch = true;
-        self
-    }
-}
-
-impl<P, T> Parser<T> for ParseAnywhere<P>
-where
-    P: Parser<T> + Sized,
-{
-    fn eval(&self, args: &mut Args) -> Result<T, Error> {
-        #[cfg(feature = "autocomplete")]
-        let mut comp_items = Vec::new();
-
-        // the idea of "anywhere" parsing is to try to parse at all the "tails"
-        // of the arguments picking either first succesful parse or the "best" error
-        //
-        // consider a parser --set <NAME> <VAL>
-        // --set name val               <- good parser
-        // --set name --bogus val       <- want to complain on bogus here
-        // --set name                   <- want to demand <val>
+        let original_scope = args.scope();
 
+        let mut best_error = if let Some(item) = first_item(&self.inner.meta()) {
+            let missing_item = MissingItem {
+                item,
+                position: original_scope.start,
+                scope: original_scope.clone(),
+            };
+            Error::Missing(vec![missing_item])
+        } else {
+            unreachable!("bpaf usage BUG: adjacent should start with a required argument");
+        };
         let mut best_args = args.clone();
-        let mut best_consumed = usize::MAX;
-        let mut best_error = Error::Missing(Vec::new());
+        let mut best_consumed = 0;
 
         for (start, mut this_arg) in args.ranges() {
             // since we only want to parse things to the right of the first item we perform
@@ -865,129 +866,104 @@ where
             // expected:
             // consider examples "42 -n" and "-n 42"
             // without multi step approach first command line also parses into 42
-
             let mut scratch = this_arg.clone();
-            #[allow(clippy::range_plus_one)] // inclusive range is the wrong type
-            scratch.restrict_to_range(&(start..start + 1));
+            scratch.set_scope(start..start + 1);
             let before = scratch.len();
-            // nothing to consume, might as well stop right now
-            if before == 0 {
-                break;
-            }
+
+            // nothing to consume, might as well skip this segment right now
             // it will most likely fail, but it doesn't matter, we are only looking for the
             // left most match
+            if before == 0 {
+                continue;
+            }
+
             let _ = self.inner.eval(&mut scratch);
+
             if before == scratch.len() {
                 // failed to consume anything which means we don't start parsing at this point
                 continue;
             }
 
-            match self.inner.eval(&mut this_arg) {
-                // managed to consume something - should make changes permanent and return it
-                //
-                // ParseFailure covers failures or --help/--version for the nested parsers
-                // anywhere shouldn't consume that
-                done @ (Ok(_) | Err(Error::ParseFailure(_))) => {
-                    this_arg.copy_usage_from(args, 0..start);
-                    std::mem::swap(&mut this_arg, args);
-                    return done;
+            this_arg.set_scope(start..original_scope.end);
+            let before = this_arg.len();
+
+            // values consumed by adjacent must be actually adjacent - if a scope contains
+            // already parsed values inside we need to trim it
+            if original_scope.end - start > before {
+                if let Some(adj_scope) = this_arg.adjacently_available_from(start) {
+                    this_arg.set_scope(adj_scope);
+                } else {
+                    continue;
                 }
+            }
 
-                // failed to find something, try to improve previous error message and resume
-                Err(err) => {
-                    if this_arg.len() < best_args.len() {
+            loop {
+                match self.inner.eval(&mut this_arg) {
+                    Ok(res) => {
+                        // there's a smaller adjacent scope, we must try it before returning.
+                        if let Some(adj_scope) = this_arg.adjacent_scope(args) {
+                            this_arg = args.clone();
+                            this_arg.set_scope(adj_scope);
+                        } else {
+                            std::mem::swap(args, &mut this_arg);
+                            args.set_scope(original_scope);
+                            return Ok(res);
+                        }
+                    }
+                    Err(err) => {
+                        let consumed = before - this_arg.len();
+                        if consumed > best_consumed {
+                            best_consumed = consumed;
+                            std::mem::swap(&mut best_args, &mut this_arg);
+                        }
                         best_error = err;
-                        best_consumed = this_arg.len();
-
-                        best_args = this_arg;
+                        break;
                     }
                 }
             }
         }
 
-        #[cfg(feature = "autocomplete")]
-        args.swap_comps_with(&mut comp_items);
-
-        // also try to run the parser with no input
-        match self.inner.eval(&mut Args::from(&[])) {
-            done @ (Ok(_) | Err(Error::ParseFailure(_))) => {
-                return done;
-            }
-            Err(err) => {
-                if best_consumed == usize::MAX {
-                    best_error = err;
-                }
+        fn first_item(meta: &Meta) -> Option<Item> {
+            match meta {
+                Meta::And(xs) => xs.first().and_then(first_item),
+                Meta::Item(item) => Some(*item.clone()),
+                Meta::Skip | Meta::Or(_) => None,
+                Meta::Optional(x)
+                | Meta::Required(x)
+                | Meta::Adjacent(x)
+                | Meta::Many(x)
+                | Meta::Decorated(x, _, _)
+                | Meta::HideUsage(x) => first_item(x),
             }
         }
 
-        // parser "anywhere" runs in a restricted context, using restricted
-        // context makes sense for error message reporting as well. Without this
-        // it would complain about unexpected flags that are unknown to self.inner, but known
-        // to the global parser.
-        //
-        // Removing unexpected things from the context limits error to "expected xxx", but
-        // breaks "perhaps you meant ..." part. So we run this logic here as well
-        let meta = self.inner.meta();
-        best_args.restrict_to_range(&(0..0));
-
-        Err(match best_error {
-            Error::Message(msg, catch) => Error::Message(msg, catch || self.catch),
-            err @ Error::ParseFailure(_) => err,
-            Error::Missing(missing) => {
-                let msg = crate::meta_youmean::suggest(&best_args, &meta)
-                    .unwrap_or_else(|| crate::help::summarize_missing(&missing, &meta, &best_args));
-                Error::Message(msg, self.catch)
-            }
-        })
+        std::mem::swap(args, &mut best_args);
+        Err(best_error)
     }
 
     fn meta(&self) -> Meta {
-        classify_anywhere(self.inner.meta())
-    }
-}
-
-// currently supported anywhere meta patterns:
-// - multi value options consisting of a required option followed by one or more positional items
-
-// anything else is
-fn classify_anywhere(meta: Meta) -> Meta {
-    if let Meta::And(xs) = &meta {
-        let mut fields = Vec::new();
-        let mut iter = xs.iter();
-
-        let (name, shorts, help) = match iter.next() {
-            Some(Meta::Item(item)) => match &**item {
-                Item::Flag {
-                    name,
-                    shorts,
-                    help,
-                    env: _,
-                } => (name, shorts, help),
-                _ => return meta,
-            },
-            _ => return meta,
-        };
+        let meta = self.inner.meta();
+        Meta::Adjacent(Box::new(meta))
+    }
+}
 
-        while let Some(Meta::Item(item)) = iter.next() {
-            if let Item::Positional {
-                metavar,
-                strict: _,
-                help,
-            } = &**item
-            {
-                fields.push((*metavar, help.clone()));
-            } else {
-                break;
-            }
-        }
-        if iter.next().is_none() {
-            return Meta::from(Item::MultiArg {
-                name: *name,
-                shorts: shorts.clone(),
-                help: help.clone(),
-                fields,
-            });
-        }
+/// Create boxed parser
+///
+/// Boxed parser doesn't expose internal representation in it's type and allows to return
+/// different parsers in different conditional branches
+///
+/// You can create it with a single argument `construct` macro or by using `boxed` annotation
+#[doc = include_str!("docs/boxed.md")]
+pub struct ParseBox<T> {
+    /// Boxed inner parser
+    pub inner: Box<dyn Parser<T>>,
+}
+
+impl<T> Parser<T> for ParseBox<T> {
+    fn eval(&self, args: &mut Args) -> Result<T, Error> {
+        self.inner.eval(args)
+    }
+    fn meta(&self) -> Meta {
+        self.inner.meta()
     }
-    Meta::Anywhere(Box::new(meta))
 }


Test patch:
diff --git a/bpaf_derive/src/field_tests.rs b/bpaf_derive/src/field_tests.rs
index d21c79c..2484b2c 100644
--- a/bpaf_derive/src/field_tests.rs
+++ b/bpaf_derive/src/field_tests.rs
@@ -604,7 +604,7 @@ fn any_field_1() {
         field: OsString
     };
     let output = quote! {
-        ::bpaf::any::<OsString>("ARG").help("help")
+        ::bpaf::any::<OsString, _, _>("ARG", Some).help("help")
     };
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
@@ -617,7 +617,7 @@ fn any_field_2() {
         String
     };
     let output = quote! {
-        ::bpaf::any::<String>("FOO").help("help")
+        ::bpaf::any::<String, _, _>("FOO", Some).help("help")
     };
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
@@ -630,7 +630,7 @@ fn any_field_3() {
         Vec<String>
     };
     let output = quote! {
-        ::bpaf::any::<String>("FOO").help("help").many()
+        ::bpaf::any::<String, _, _>("FOO", Some).help("help").many()
     };
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
@@ -643,7 +643,33 @@ fn any_field_4() {
         Vec<OsString>
     };
     let output = quote! {
-        ::bpaf::any::<OsString>("FOO").help("help").many()
+        ::bpaf::any::<OsString, _, _>("FOO", Some).help("help").many()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
+
+#[test]
+fn any_field_5() {
+    let input: UnnamedField = parse_quote! {
+        #[bpaf(any("FOO", check))]
+        /// help
+        Vec<OsString>
+    };
+    let output = quote! {
+        ::bpaf::any::<OsString, _, _>("FOO", check).help("help").many()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
+
+#[test]
+fn any_field_6() {
+    let input: UnnamedField = parse_quote! {
+        #[bpaf(any("FOO", |x| (x == "--lit").then_some(())))]
+        /// help
+        ()
+    };
+    let output = quote! {
+        ::bpaf::any::<(), _, _>("FOO", |x| (x == "--lit").then_some(())).help("help")
     };
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
diff --git a/bpaf_derive/src/top_tests.rs b/bpaf_derive/src/top_tests.rs
index b6bb011..ca4ad71 100644
--- a/bpaf_derive/src/top_tests.rs
+++ b/bpaf_derive/src/top_tests.rs
@@ -700,56 +700,6 @@ fn adjacent_for_struct() {
     assert_eq!(top.to_token_stream().to_string(), expected.to_string());
 }
 
-#[test]
-fn anywhere_for_struct() {
-    let top: Top = parse_quote! {
-        #[bpaf(adjacent, anywhere)]
-        struct Opts {
-            a: String,
-            b: String,
-        }
-    };
-
-    let expected = quote! {
-        fn opts() -> impl ::bpaf::Parser<Opts> {
-            #[allow(unused_imports)]
-            use ::bpaf::Parser;
-            {
-                let a = ::bpaf::short('a').argument::<String>("ARG");
-                let b = ::bpaf::short('b').argument::<String>("ARG");
-                ::bpaf::construct!(Opts { a, b })
-            }.adjacent().anywhere()
-        }
-    };
-
-    assert_eq!(top.to_token_stream().to_string(), expected.to_string());
-}
-
-#[test]
-fn anywhere_catch_for_struct() {
-    let top: Top = parse_quote! {
-        #[bpaf(adjacent, anywhere, catch)]
-        struct Opts {
-            a: String,
-            b: String,
-        }
-    };
-
-    let expected = quote! {
-        fn opts() -> impl ::bpaf::Parser<Opts> {
-            #[allow(unused_imports)]
-            use ::bpaf::Parser;
-            {
-                let a = ::bpaf::short('a').argument::<String>("ARG");
-                let b = ::bpaf::short('b').argument::<String>("ARG");
-                ::bpaf::construct!(Opts { a, b })
-            }.adjacent().anywhere().catch()
-        }
-    };
-
-    assert_eq!(top.to_token_stream().to_string(), expected.to_string());
-}
-
 #[test]
 fn box_for_struct() {
     let top: Top = parse_quote! {
diff --git a/tests/adjacent.rs b/tests/adjacent.rs
index 1f02b40..2700309 100644
--- a/tests/adjacent.rs
+++ b/tests/adjacent.rs
@@ -10,8 +10,16 @@ fn test_adjacent() {
     let r = parser
         .run_inner(Args::from(&["-a", "-c", "-a", "-b", "-a", "-b", "-c"]))
         .unwrap();
+    // adjacent groups here argument
+    // -a [-b] -c  | -a -b [-c] | -a -b -c
     assert_eq!(r, &[((), false, true), ((), true, false), ((), true, true)]);
 
+    let r = parser.run_inner(Args::from(&["-a"])).unwrap();
+    assert_eq!(r, &[((), false, false)]);
+
+    let r = parser.run_inner(Args::from(&["-a", "-c"])).unwrap();
+    assert_eq!(r, &[((), false, true)]);
+
     let r = parser.run_inner(Args::from(&[])).unwrap();
     assert_eq!(r, &[]);
 }
@@ -85,10 +93,7 @@ fn adjacent_error_message_pos_many() {
         .run_inner(Args::from(&["-a", "10"]))
         .unwrap_err()
         .unwrap_stderr();
-    assert_eq!(
-        r,
-        "Expected <C>, got \"-a\". Pass --help for usage information"
-    );
+    assert_eq!(r, "Expected <C>, pass --help for usage information");
 }
 
 #[test]
@@ -104,8 +109,105 @@ fn adjacent_error_message_arg_many() {
         .run_inner(Args::from(&["-a", "10"]))
         .unwrap_err()
         .unwrap_stderr();
+    // this should ask for -b or -c and complain on 10...
     assert_eq!(
         r,
-        "Expected -b <B>, got \"-a\". Pass --help for usage information"
+        "Expected -b <B>, got \"10\". Pass --help for usage information"
     );
 }
+
+#[test]
+fn adjacent_is_adjacent() {
+    let a = short('a').req_flag(());
+    let b = positional::<usize>("B");
+    let parser = construct!(a, b).adjacent().map(|t| t.1).many().to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "-a", "10", "20"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected <B>, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "10", "-a", "20"]))
+        .unwrap();
+    assert_eq!(r, [10, 20]);
+}
+
+#[test]
+fn adjacent_with_switch() {
+    let a = short('a').req_flag(());
+    let b = positional::<usize>("B");
+    let ab = construct!(a, b).adjacent().map(|t| t.1).many();
+    let c = short('c').switch();
+    let parser = construct!(ab, c).to_options();
+
+    let r = parser.run_inner(Args::from(&["-a", "10", "-c"])).unwrap();
+    assert_eq!(r, (vec![10], true));
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "10", "-c", "-a", "20"]))
+        .unwrap();
+    assert_eq!(r, (vec![10, 20], true));
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a", "10", "-a", "20"]))
+        .unwrap();
+    assert_eq!(r, (vec![10, 20], true));
+}
+
+#[test]
+fn adjacent_limits_commands() {
+    let x = pure(()).to_options().command("a").adjacent();
+    let s = short('s').switch();
+    let parser = construct!(s, x).to_options();
+
+    let r = parser.run_inner(Args::from(&["a", "-s"])).unwrap();
+    assert_eq!(r, (true, ()));
+}
+
+#[test]
+fn commands_and_adjacent() {
+    let eat = positional::<String>("FOOD")
+        .to_options()
+        .command("eat")
+        .help("eat something")
+        .adjacent();
+
+    let sleep = long("time")
+        .argument::<String>("HOURS")
+        .to_options()
+        .command("sleep")
+        .help("sleep for a bit")
+        .adjacent();
+
+    let cmds = construct!([eat, sleep]);
+    let switch = short('s').switch();
+
+    let parser = construct!(switch, cmds).to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["sleep", "--time", "12", "-s"]))
+        .unwrap();
+    assert_eq!(r, (true, "12".to_owned()));
+
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+
+    // TODO - this is ugly
+    let expected = "\
+Usage: [-s] COMMAND ...
+
+Available options:
+    -s
+    -h, --help  Prints help information
+
+Available commands:
+    eat    eat something
+    sleep  sleep for a bit
+";
+
+    assert_eq!(r, expected);
+}
diff --git a/tests/anywhere.rs b/tests/anywhere.rs
index 2080fee..cf5e2c9 100644
--- a/tests/anywhere.rs
+++ b/tests/anywhere.rs
@@ -2,11 +2,11 @@ use bpaf::*;
 
 #[test]
 fn parse_anywhere_positional() {
-    let a = any::<String>("x")
-        .guard(|h| h != "--help", "ignore help")
+    let a = any::<String, _, _>("X", |h| if h != "--help" { Some(h) } else { None })
+        .help("all the things")
         .anywhere();
 
-    let b = short('b').switch();
+    let b = short('b').help("batch mode").switch();
     let parser = construct!(a, b).to_options();
 
     let r = parser
@@ -14,10 +14,16 @@ fn parse_anywhere_positional() {
         .unwrap_err()
         .unwrap_stdout();
 
-    assert_eq!(
-        r,
-        "Usage: <x> [-b]\n\nAvailable options:\n    -b\n    -h, --help  Prints help information\n"
-    );
+    let expected = "\
+Usage: <X> [-b]
+
+Available options:
+    <X>         all the things
+    -b          batch mode
+    -h, --help  Prints help information
+";
+
+    assert_eq!(r, expected);
     // this should be allowed because "anywhere" prevents anything inside from being positional
     parser.check_invariants(true);
 }
@@ -26,7 +32,7 @@ fn parse_anywhere_positional() {
 fn parse_anywhere_no_catch() {
     let a = short('a').req_flag(());
     let b = positional::<usize>("x");
-    let ab = construct!(a, b).anywhere();
+    let ab = construct!(a, b).adjacent();
     let c = short('c').switch();
     let parser = construct!(ab, c).to_options();
 
@@ -66,7 +72,10 @@ fn parse_anywhere_no_catch() {
         .run_inner(Args::from(&["-a", "-c"]))
         .unwrap_err()
         .unwrap_stderr();
-    assert_eq!(r, "Expected <x>, pass --help for usage information");
+    assert_eq!(
+        r,
+        "Expected <x>, got \"-c\". Pass --help for usage information"
+    );
 
     let r = parser
         .run_inner(Args::from(&["-a", "221b", "-c"]))
@@ -79,7 +88,7 @@ fn parse_anywhere_no_catch() {
 fn anywhere_catch_optional() {
     let a = short('a').req_flag(());
     let b = positional::<usize>("x");
-    let ab = construct!(a, b).anywhere().catch().optional();
+    let ab = construct!(a, b).adjacent().optional().catch();
     let bc = short('a').switch();
     let parser = construct!(ab, bc).to_options();
 
@@ -97,16 +106,17 @@ fn anywhere_catch_optional() {
 fn anywhere_catch_many() {
     let a = short('a').req_flag(());
     let b = positional::<usize>("x");
-    let ab = construct!(a, b).anywhere().catch().many();
+    let ab = construct!(a, b).adjacent().many().catch();
     let bc = short('a').switch();
     let parser = construct!(ab, bc).to_options();
 
+    let r = parser.run_inner(Args::from(&["-a"])).unwrap();
+
+    assert_eq!(r, (vec![], true));
+
     let r = parser.run_inner(Args::from(&["-a", "10"])).unwrap();
     assert_eq!(r, (vec![((), 10)], false));
 
-    let r = parser.run_inner(Args::from(&["-a"])).unwrap();
-    assert_eq!(r, (Vec::new(), true));
-
     let r = parser.run_inner(Args::from(&[])).unwrap();
     assert_eq!(r, (Vec::new(), false));
 }
@@ -115,7 +125,7 @@ fn anywhere_catch_many() {
 fn anywhere_catch_fallback() {
     let a = short('a').req_flag(());
     let b = positional::<usize>("x");
-    let ab = construct!(a, b).anywhere().catch().fallback(((), 10));
+    let ab = construct!(a, b).adjacent().fallback(((), 10));
     let bc = short('a').switch();
     let parser = construct!(ab, bc).to_options();
 
@@ -130,64 +140,20 @@ fn anywhere_catch_fallback() {
 }
 
 #[test]
-fn parse_anywhere_catch_required() {
+fn parse_anywhere_catch_optional() {
     let a = short('a').req_flag(());
     let b = positional::<usize>("x");
-    let ab = construct!(a, b).anywhere().catch();
+
+    // optional + catch makes it so parser succeeds without consuming anything
+    let ab = construct!(a, b).adjacent().optional().catch();
     let c = short('c').switch();
     let parser = construct!(ab, c).to_options();
 
-    let r = parser
-        .run_inner(Args::from(&["-c", "-a"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    // this should complain about unexpected -a
-    assert_eq!(r, "Expected <x>, pass --help for usage information");
-
-    let r = parser
-        .run_inner(Args::from(&["-a"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    assert_eq!(r, "Expected <x>, pass --help for usage information");
-
     let r = parser
         .run_inner(Args::from(&["-a", "221b"]))
         .unwrap_err()
         .unwrap_stderr();
-    assert_eq!(r, "Couldn't parse \"221b\": invalid digit found in string");
-
-    let r = parser
-        .run_inner(Args::from(&["-c", "-a", "221b"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    assert_eq!(r, "Couldn't parse \"221b\": invalid digit found in string");
-
-    let r = parser
-        .run_inner(Args::from(&["-a", "-c"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    assert_eq!(r, "Expected <x>, pass --help for usage information");
-
-    let r = parser
-        .run_inner(Args::from(&["-a", "221b", "-c"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    assert_eq!(r, "Couldn't parse \"221b\": invalid digit found in string");
-
-    let r = parser
-        .run_inner(Args::from(&["3", "-a"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    assert_eq!(r, "Expected <x>, pass --help for usage information");
-}
-
-#[test]
-fn parse_anywhere_catch_optional() {
-    let a = short('a').req_flag(());
-    let b = positional::<usize>("x");
-    let ab = construct!(a, b).anywhere().catch().optional();
-    let c = short('c').switch();
-    let parser = construct!(ab, c).to_options();
+    assert_eq!(r, "-a is not expected in this context");
 
     let r = parser
         .run_inner(Args::from(&["3", "-a"]))
@@ -201,12 +167,6 @@ fn parse_anywhere_catch_optional() {
         .unwrap_stderr();
     assert_eq!(r, "-a is not expected in this context");
 
-    let r = parser
-        .run_inner(Args::from(&["-a", "221b"]))
-        .unwrap_err()
-        .unwrap_stderr();
-    assert_eq!(r, "-a is not expected in this context");
-
     let r = parser
         .run_inner(Args::from(&["-c", "-a"]))
         .unwrap_err()
@@ -234,22 +194,22 @@ fn parse_anywhere_catch_optional() {
 
 #[test]
 fn anywhere_literal() {
-    let tag = any::<String>("-mode").guard(|x| x == "-mode", "not mode");
+    let tag = any::<String, _, _>("-mode", |x| if x == "-mode" { Some(()) } else { None });
     let mode = positional::<usize>("value");
-    let a = construct!(tag, mode).anywhere().catch().many();
+    let a = construct!(tag, mode).adjacent().many().catch();
     let b = short('b').switch();
     let parser = construct!(a, b).to_options();
 
     let r = parser
         .run_inner(Args::from(&["-b", "-mode", "12"]))
         .unwrap();
-    assert_eq!(r, (vec![("-mode".to_owned(), 12)], true));
+    assert_eq!(r, (vec![((), 12)], true));
 
     let r = parser
         .run_inner(Args::from(&["-mode", "12", "-b"]))
         .unwrap();
-    assert_eq!(r, (vec![("-mode".to_owned(), 12)], true));
+    assert_eq!(r, (vec![((), 12)], true));
 
     let r = parser.run_inner(Args::from(&["-mode", "12"])).unwrap();
-    assert_eq!(r, (vec![("-mode".to_owned(), 12)], false));
+    assert_eq!(r, (vec![((), 12)], false));
 }
diff --git a/tests/completion.rs b/tests/completion.rs
index bb227c6..84fc14d 100644
--- a/tests/completion.rs
+++ b/tests/completion.rs
@@ -12,11 +12,13 @@ fn static_complete_test_1() {
 
     let parser = construct!(a, b, bb, c).to_options();
 
+    // this used to be disambiguation, not anymore
+    /*
     let r = parser
         .run_inner(Args::from(&["-vvvv"]).set_comp(1))
         .unwrap_err()
         .unwrap_stdout();
-    assert_eq!(r, "-vvvv\n");
+    assert_eq!(r, "-vvvv\n");*/
 
     let r = parser
         .run_inner(Args::from(&["--"]).set_comp(1))
diff --git a/tests/errors.rs b/tests/errors.rs
index 37111f5..681b01b 100644
--- a/tests/errors.rs
+++ b/tests/errors.rs
@@ -49,3 +49,23 @@ fn guard_message() {
 
     assert_eq!(res, "\"30\": too high");
 }
+
+#[test]
+fn cannot_be_used_multiple_times() {
+    let parser = short('a').switch().to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["-aaa"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    let expected = "-a is not expected in this context";
+    assert_eq!(r, expected);
+
+    // TODO - improve error message
+    let r = parser
+        .run_inner(Args::from(&["-a", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    let expected = "-a is not expected in this context";
+    assert_eq!(r, expected);
+}
diff --git a/tests/help_format.rs b/tests/help_format.rs
index 64e8757..8bacbd4 100644
--- a/tests/help_format.rs
+++ b/tests/help_format.rs
@@ -197,47 +197,54 @@ Available options:
 fn anywhere_invariant_check() {
     #[derive(Debug, Clone, Bpaf)]
     #[allow(dead_code)]
-    #[bpaf(anywhere)]
+    #[bpaf(adjacent)]
     struct Fooo {
         tag: (),
-        #[bpaf(positional)]
-        /// help
+        #[bpaf(positional("NAME"))]
+        /// help for name
         name: String,
-        #[bpaf(positional)]
+        #[bpaf(positional("VAL"))]
+        /// help for val
         val: String,
     }
 
-    let a = short('a').switch();
-    let b = short('b').switch();
+    let a = short('a').help("help for a").switch();
+    let b = short('b').help("help for b").switch();
     let parser = construct!(a, fooo(), b).to_options();
 
-    parser.check_invariants(true);
-
     let expected = "\
-Usage: [-a] --tag ARG ARG [-b]
+Usage: [-a] --tag <NAME> <VAL> [-b]
 
 Available options:
-    -a
-        --tag <ARG> <ARG>
-            <ARG>  help
-    -b
-    -h, --help     Prints help information
+    -a          help for a
+  --tag <NAME> <VAL>
+    <NAME>      help for name
+    <VAL>       help for val
+
+    -b          help for b
+    -h, --help  Prints help information
 ";
+
     let r = parser
         .run_inner(Args::from(&["--help"]))
         .unwrap_err()
         .unwrap_stdout();
     assert_eq!(r, expected);
+
+    // this shouldn't crash
+    parser.check_invariants(true);
 }
 
 #[test]
 fn multi_arg_help() {
-    let a = long("flag").help("flag help").req_flag(());
-    let b = positional::<String>("NAME").help("pos1 help");
-    let c = positional::<bool>("STATE").help("pos2 help");
-    let combo = construct!(a, b, c).anywhere();
+    let a = short('f').long("flag").help("flag help").req_flag(());
+    let b = short('e').long("extra").help("extra strange").switch();
+    let c = positional::<String>("NAME").help("pos1 help");
+    let d = positional::<bool>("STATE").help("pos2 help");
+    let combo = construct!(a, b, c, d).adjacent().optional();
     let verbose = short('v').long("verbose").help("verbose").switch();
-    let parser = construct!(verbose, combo).to_options();
+    let detailed = long("detailed").short('d').help("detailed").switch();
+    let parser = construct!(verbose, combo, detailed).to_options();
 
     let r = parser
         .run_inner(Args::from(&["--help"]))
@@ -245,14 +252,47 @@ fn multi_arg_help() {
         .unwrap_stdout();
 
     let expected = "\
-Usage: [-v] --flag NAME STATE
+Usage: [-v] [-f [-e] <NAME> <STATE>] [-d]
 
 Available options:
-    -v, --verbose    verbose
-        --flag <NAME> <STATE>  flag help
-            <NAME>   pos1 help
-            <STATE>  pos2 help
-    -h, --help       Prints help information
+    -v, --verbose   verbose
+  -f [-e] <NAME> <STATE>
+    -f, --flag      flag help
+    -e, --extra     extra strange
+    <NAME>          pos1 help
+    <STATE>         pos2 help
+
+    -d, --detailed  detailed
+    -h, --help      Prints help information
+";
+
+    assert_eq!(r, expected);
+}
+
+#[test]
+fn multi_pos_help() {
+    let a = positional::<String>("NAME").help("name help");
+    let b = positional::<String>("VAL").help("val help");
+    let combo = construct!(a, b).adjacent();
+    let verbose = short('v').long("verbose").switch();
+    let parser = construct!(verbose, combo).to_options();
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+
+    let expected = "\
+Usage: [-v] <NAME> <VAL>
+
+Available positional items:
+  <NAME> <VAL>
+    <NAME>  name help
+    <VAL>   val help
+
+
+Available options:
+    -v, --verbose
+    -h, --help     Prints help information
 ";
     assert_eq!(r, expected);
 }
diff --git a/tests/meta_usage.rs b/tests/meta_usage.rs
index a7d0fe2..7a0ef5e 100644
--- a/tests/meta_usage.rs
+++ b/tests/meta_usage.rs
@@ -262,6 +262,16 @@ fn a_or_b_opt_and_c() {
     assert_usage(parser, "[-a A | -b B] <C>");
 }
 
+#[test]
+fn any_in_adjacent() {
+    let a = short('a').req_flag(());
+    let b = any::<i64, _, _>("A", Some);
+    let parser = construct!(a, b).adjacent().to_options();
+
+    // TODO - ideally we want "-a A"
+    assert_usage(parser, "-a <A>");
+}
+
 #[test]
 fn positionals_in_branches_are_okay() {
     let a = short('a').argument::<String>("A");
diff --git a/tests/params.rs b/tests/params.rs
index 30968b0..9e0929a 100644
--- a/tests/params.rs
+++ b/tests/params.rs
@@ -5,7 +5,7 @@ use bpaf::*;
 #[test]
 fn get_any_simple() {
     let a = short('a').switch();
-    let b = any::<OsString>("REST").help("any help");
+    let b = any::<OsString, _, _>("REST", Some).help("any help");
     let parser = construct!(a, b).to_options();
 
     let r = parser.run_inner(Args::from(&["-a", "-b"])).unwrap().1;
@@ -21,7 +21,7 @@ fn get_any_simple() {
 #[test]
 fn get_any_many() {
     let a = short('a').switch();
-    let b = any::<OsString>("REST").help("any help").many();
+    let b = any::<OsString, _, _>("REST", Some).help("any help").many();
     let parser = construct!(a, b).to_options();
 
     let r = parser.run_inner(Args::from(&["-a", "-b"])).unwrap();
@@ -36,7 +36,7 @@ fn get_any_many() {
 
 #[test]
 fn get_any_many2() {
-    let parser = any::<OsString>("REST").many().to_options();
+    let parser = any::<OsString, _, _>("REST", Some).many().to_options();
 
     let r = parser.run_inner(Args::from(&["-vvv"])).unwrap();
     assert_eq!(r[0], "-vvv");
diff --git a/tests/positionals.rs b/tests/positionals.rs
index a7acdee..6c2037c 100644
--- a/tests/positionals.rs
+++ b/tests/positionals.rs
@@ -71,20 +71,29 @@ fn helpful_error_message() {
 #[test]
 fn positional_argument() {
     set_override(false);
-    let p = positional::<String>("FILE").group_help("File to process");
+    let p = positional::<String>("FILE")
+        .help("file name")
+        .group_help("File to process");
     let parser = p.to_options();
 
     let help = parser
         .run_inner(Args::from(&["--help"]))
         .unwrap_err()
         .unwrap_stdout();
-    let expected_help = "\
+
+    let expected = "\
 Usage: <FILE>
 
+Available positional items:
+  File to process
+    <FILE>  file name
+
+
 Available options:
     -h, --help  Prints help information
 ";
-    assert_eq!(expected_help, help);
+
+    assert_eq!(expected, help);
 }
 
 #[test]
diff --git a/tests/tests.rs b/tests/tests.rs
index 27a39c1..58d66f4 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1849,8 +1849,8 @@ fn optional_bool_states() {
 #[test]
 fn fancy_negative() {
     let a = short('a').req_flag(());
-    let b = any::<i64>("A");
-    let ab = construct!(a, b).adjacent().anywhere().map(|x| x.1);
+    let b = any::<i64, _, _>("A", Some);
+    let ab = construct!(a, b).adjacent().map(|x| x.1);
 
     let c = short('c').argument::<usize>("C").fallback(42);
 
@@ -1869,11 +1869,13 @@ fn fancy_negative() {
         .unwrap_err()
         .unwrap_stdout();
 
+    // TODO - rendering sucks once you start inventing fancy combinations and don't provide help...
     let expected = "\
-Usage: -a A [-c C]
+Usage: -a <A> [-c C]
 
 Available options:
-    -a <A>
+  -a <A>
+
     -c <C>
     -h, --help  Prints help information
 ";
@@ -1915,10 +1917,7 @@ fn option_requires_other_option1() {
         .unwrap_err()
         .unwrap_stderr();
     // error message sucks...
-    assert_eq!(
-        r,
-        "Expected -b <B>, got \"-a\". Pass --help for usage information"
-    );
+    assert_eq!(r, "Expected -b <B>, pass --help for usage information");
 }
 
 #[test]
@@ -1948,8 +1947,14 @@ fn default_for_some() {
 }
 
 #[test]
-fn anywhere_can_consume_nothing() {
-    let parser = positional::<String>("args").many().anywhere().to_options();
-    let r = parser.run_inner(Args::from(&[])).unwrap();
-    assert!(r.is_empty());
+fn adjacent_anywhere_needs_to_consume_something() {
+    let a = short('a').switch();
+    let b = short('b').switch();
+    let parser = construct!(a, b).adjacent().to_options();
+
+    let r = parser.run_inner(Args::from(&["-a"])).unwrap();
+    assert_eq!(r, (true, false));
+
+    let r = parser.run_inner(Args::from(&["-b"])).unwrap();
+    assert_eq!(r, (false, true));
 }