Merge pull request #3635 from epage/port

feat: Expose clap_lex
This commit is contained in:
Ed Page 2022-04-15 14:00:28 -05:00 committed by GitHub
commit 7849c35a3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1362 additions and 174 deletions

View file

@ -1,6 +1,7 @@
[workspace] [workspace]
members = [ members = [
"clap_derive", "clap_derive",
"clap_lex",
"clap_complete", "clap_complete",
"clap_complete_fig", "clap_complete_fig",
"clap_mangen", "clap_mangen",
@ -118,11 +119,11 @@ path = "benches/06_rustup.rs"
[dependencies] [dependencies]
clap_derive = { path = "./clap_derive", version = "3.1.7", optional = true } clap_derive = { path = "./clap_derive", version = "3.1.7", optional = true }
clap_lex = { path = "./clap_lex", version = "0.1.0" }
bitflags = "1.2" bitflags = "1.2"
textwrap = { version = "0.15.0", default-features = false, features = [] } textwrap = { version = "0.15.0", default-features = false, features = [] }
unicase = { version = "2.6", optional = true } unicase = { version = "2.6", optional = true }
indexmap = "1.0" indexmap = "1.0"
os_str_bytes = "6.0"
strsim = { version = "0.10", optional = true } strsim = { version = "0.10", optional = true }
yaml-rust = { version = "0.4.1", optional = true } yaml-rust = { version = "0.4.1", optional = true }
atty = { version = "0.2", optional = true } atty = { version = "0.2", optional = true }

View file

@ -120,6 +120,7 @@ Why use the procedural [Builder API](https://github.com/clap-rs/clap/blob/v3.1.8
- [wild](https://crates.io/crates/wild) for supporting wildcards (`*`) on Windows like you do Linux - [wild](https://crates.io/crates/wild) for supporting wildcards (`*`) on Windows like you do Linux
- [argfile](https://crates.io/crates/argfile) for loading additional arguments from a file (aka response files) - [argfile](https://crates.io/crates/argfile) for loading additional arguments from a file (aka response files)
- [shadow-rs](https://crates.io/crates/shadow-rs) for generating `Command::long_version` - [shadow-rs](https://crates.io/crates/shadow-rs) for generating `Command::long_version`
- [clap_lex](https://crates.io/crates/clap_lex) for a lighter-weight, battle-tested CLI parser
- [clap_mangen](https://crates.io/crates/clap_mangen) for generating man page source (roff) - [clap_mangen](https://crates.io/crates/clap_mangen) for generating man page source (roff)
- [clap_complete](https://crates.io/crates/clap_complete) for shell completion support - [clap_complete](https://crates.io/crates/clap_complete) for shell completion support
- [clap-verbosity-flag](https://crates.io/crates/clap-verbosity-flag) - [clap-verbosity-flag](https://crates.io/crates/clap-verbosity-flag)

11
clap_lex/CHANGELOG.md Normal file
View file

@ -0,0 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
<!-- next-header -->
## [Unreleased] - ReleaseDate
<!-- next-url -->
[Unreleased]: https://github.com/clap-rs/clap/compare/ce71b08a3fe28c640dc6e17f6f5bb1452bd6d6d8...HEAD

3
clap_lex/CONTRIBUTING.md Normal file
View file

@ -0,0 +1,3 @@
# How to Contribute
See the [clap-wide CONTRIBUTING.md](../CONTRIBUTING.md). This will contain `clap_lex` specific notes.

39
clap_lex/Cargo.toml Normal file
View file

@ -0,0 +1,39 @@
[package]
name = "clap_lex"
version = "0.1.0"
edition = "2018"
include = [
"src/**/*",
"Cargo.toml",
"LICENSE-*",
"README.md"
]
description = "Minimal, flexible command line parser"
repository = "https://github.com/clap-rs/clap/tree/master/clap_lex"
documentation = "https://docs.rs/clap_lex"
keywords = [
"argument",
"cli",
"arg",
"parser",
"parse"
]
categories = ["command-line-interface"]
license = "MIT OR Apache-2.0"
readme = "README.md"
[package.metadata.release]
pre-release-replacements = [
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
{file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1},
{file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/clap-rs/clap/compare/{{tag_name}}...HEAD", exactly=1},
{file="README.md", search="github.com/clap-rs/clap/blob/[^/]+/", replace="github.com/clap-rs/clap/blob/{{tag_name}}/", exactly=4, prerelease = true},
]
[lib]
bench = false
[dependencies]
os_str_bytes = "6.0"

201
clap_lex/LICENSE-APACHE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
clap_lex/LICENSE-MIT Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2016 Kevin B. Knapp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

19
clap_lex/README.md Normal file
View file

@ -0,0 +1,19 @@
<!-- omit in TOC -->
# clap_lex
> **Minimal, flexible command line parser**
[![Crates.io](https://img.shields.io/crates/v/clap_lex?style=flat-square)](https://crates.io/crates/clap_lex)
[![Crates.io](https://img.shields.io/crates/d/clap_lex?style=flat-square)](https://crates.io/crates/clap_lex)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](https://github.com/clap-rs/clap/blob/clap_lex-v3.1.1/LICENSE-APACHE)
[![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/clap-rs/clap/blob/clap_lex-v3.1.1/LICENSE-MIT)
Dual-licensed under [Apache 2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT).
1. [About](#about)
2. [API Reference](https://docs.rs/clap_lex)
3. [Questions & Discussions](https://github.com/clap-rs/clap/discussions)
4. [CONTRIBUTING](https://github.com/clap-rs/clap/blob/clap_lex-v3.1.1/clap_lex/CONTRIBUTING.md)
5. [Sponsors](https://github.com/clap-rs/clap/blob/clap_lex-v3.1.1/README.md#sponsors)
## About

472
clap_lex/src/lib.rs Normal file
View file

@ -0,0 +1,472 @@
//! Minimal, flexible command-line parser
//!
//! As opposed to a declarative parser, this processes arguments as a stream of tokens. As lexing
//! a command-line is not context-free, we rely on the caller to decide how to interpret the
//! arguments.
//!
//! # Examples
//!
//! ```rust
//! # use std::path::PathBuf;
//! # type BoxedError = Box<dyn std::error::Error + Send + Sync>;
//! #[derive(Debug)]
//! struct Args {
//! paths: Vec<PathBuf>,
//! color: Color,
//! verbosity: usize,
//! }
//!
//! #[derive(Debug)]
//! enum Color {
//! Always,
//! Auto,
//! Never,
//! }
//!
//! impl Color {
//! fn parse(s: Option<&clap_lex::RawOsStr>) -> Result<Self, BoxedError> {
//! let s = s.map(|s| s.to_str().ok_or(s));
//! match s {
//! Some(Ok("always")) | Some(Ok("")) | None => {
//! Ok(Color::Always)
//! }
//! Some(Ok("auto")) => {
//! Ok(Color::Auto)
//! }
//! Some(Ok("never")) => {
//! Ok(Color::Never)
//! }
//! Some(invalid) => {
//! Err(format!("Invalid value for `--color`, {:?}", invalid).into())
//! }
//! }
//! }
//! }
//!
//! fn parse_args(
//! raw: impl IntoIterator<Item=impl Into<std::ffi::OsString>>
//! ) -> Result<Args, BoxedError> {
//! let mut args = Args {
//! paths: Vec::new(),
//! color: Color::Auto,
//! verbosity: 0,
//! };
//!
//! let raw = clap_lex::RawArgs::new(raw);
//! let mut cursor = raw.cursor();
//! raw.next(&mut cursor); // Skip the bin
//! while let Some(arg) = raw.next(&mut cursor) {
//! if arg.is_escape() {
//! args.paths.extend(raw.remaining(&mut cursor).map(PathBuf::from));
//! } else if arg.is_stdio() {
//! args.paths.push(PathBuf::from("-"));
//! } else if let Some((long, value)) = arg.to_long() {
//! match long {
//! Ok("verbose") => {
//! if let Some(value) = value {
//! return Err(format!("`--verbose` does not take a value, got `{:?}`", value).into());
//! }
//! args.verbosity += 1;
//! }
//! Ok("color") => {
//! args.color = Color::parse(value)?;
//! }
//! _ => {
//! return Err(
//! format!("Unexpected flag: --{}", arg.display()).into()
//! );
//! }
//! }
//! } else if let Some(mut shorts) = arg.to_short() {
//! while let Some(short) = shorts.next_flag() {
//! match short {
//! Ok('v') => {
//! args.verbosity += 1;
//! }
//! Ok('c') => {
//! let value = shorts.next_value_os();
//! args.color = Color::parse(value)?;
//! }
//! Ok(c) => {
//! return Err(format!("Unexpected flag: -{}", c).into());
//! }
//! Err(e) => {
//! return Err(format!("Unexpected flag: -{}", e.to_str_lossy()).into());
//! }
//! }
//! }
//! } else {
//! args.paths.push(PathBuf::from(arg.to_value_os().to_os_str().into_owned()));
//! }
//! }
//!
//! Ok(args)
//! }
//!
//! let args = parse_args(["bin", "--hello", "world"]);
//! println!("{:?}", args);
//! ```
use std::ffi::OsStr;
use std::ffi::OsString;
pub use std::io::SeekFrom;
pub use os_str_bytes::RawOsStr;
pub use os_str_bytes::RawOsString;
/// Command-line arguments
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct RawArgs {
items: Vec<OsString>,
}
impl RawArgs {
//// Create an argument list to parse
///
/// **NOTE:** The argument returned will be the current binary.
///
/// # Example
///
/// ```rust,no_run
/// # use std::path::PathBuf;
/// let raw = clap_lex::RawArgs::from_args();
/// let mut cursor = raw.cursor();
/// let _bin = raw.next_os(&mut cursor);
///
/// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
/// println!("{:?}", paths);
/// ```
pub fn from_args() -> Self {
Self::new(std::env::args_os())
}
//// Create an argument list to parse
///
/// # Example
///
/// ```rust,no_run
/// # use std::path::PathBuf;
/// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]);
/// let mut cursor = raw.cursor();
/// let _bin = raw.next_os(&mut cursor);
///
/// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
/// println!("{:?}", paths);
/// ```
pub fn new(iter: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self {
let iter = iter.into_iter();
Self::from(iter)
}
/// Create a cursor for walking the arguments
///
/// # Example
///
/// ```rust,no_run
/// # use std::path::PathBuf;
/// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]);
/// let mut cursor = raw.cursor();
/// let _bin = raw.next_os(&mut cursor);
///
/// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
/// println!("{:?}", paths);
/// ```
pub fn cursor(&self) -> ArgCursor {
ArgCursor::new()
}
/// Advance the cursor, returning the next [`ParsedArg`]
pub fn next(&self, cursor: &mut ArgCursor) -> Option<ParsedArg<'_>> {
self.next_os(cursor).map(ParsedArg::new)
}
/// Advance the cursor, returning a raw argument value.
pub fn next_os(&self, cursor: &mut ArgCursor) -> Option<&OsStr> {
let next = self.items.get(cursor.cursor).map(|s| s.as_os_str());
cursor.cursor = cursor.cursor.saturating_add(1);
next
}
/// Return the next [`ParsedArg`]
pub fn peek(&self, cursor: &ArgCursor) -> Option<ParsedArg<'_>> {
self.peek_os(cursor).map(ParsedArg::new)
}
/// Return a raw argument value.
pub fn peek_os(&self, cursor: &ArgCursor) -> Option<&OsStr> {
self.items.get(cursor.cursor).map(|s| s.as_os_str())
}
/// Return all remaining raw arguments, advancing the cursor to the end
///
/// # Example
///
/// ```rust,no_run
/// # use std::path::PathBuf;
/// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]);
/// let mut cursor = raw.cursor();
/// let _bin = raw.next_os(&mut cursor);
///
/// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
/// println!("{:?}", paths);
/// ```
pub fn remaining(&self, cursor: &mut ArgCursor) -> impl Iterator<Item = &OsStr> {
let remaining = self.items[cursor.cursor..].iter().map(|s| s.as_os_str());
cursor.cursor = self.items.len();
remaining
}
/// Adjust the cursor's position
pub fn seek(&self, cursor: &mut ArgCursor, pos: SeekFrom) {
let pos = match pos {
SeekFrom::Start(pos) => pos,
SeekFrom::End(pos) => (self.items.len() as i64).saturating_add(pos).max(0) as u64,
SeekFrom::Current(pos) => (cursor.cursor as i64).saturating_add(pos).max(0) as u64,
};
let pos = (pos as usize).min(self.items.len());
cursor.cursor = pos;
}
/// Inject arguments before the [`RawArgs::next`]
pub fn insert(&mut self, cursor: &ArgCursor, insert_items: &[&str]) {
self.items.splice(
cursor.cursor..cursor.cursor,
insert_items.iter().map(OsString::from),
);
}
}
impl<I, T> From<I> for RawArgs
where
I: Iterator<Item = T>,
T: Into<OsString>,
{
fn from(val: I) -> Self {
Self {
items: val.map(|x| x.into()).collect(),
}
}
}
/// Position within [`RawArgs`]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ArgCursor {
cursor: usize,
}
impl ArgCursor {
fn new() -> Self {
Self { cursor: 0 }
}
}
/// Command-line Argument
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ParsedArg<'s> {
inner: std::borrow::Cow<'s, RawOsStr>,
utf8: Option<&'s str>,
}
impl<'s> ParsedArg<'s> {
fn new(inner: &'s OsStr) -> Self {
let utf8 = inner.to_str();
let inner = RawOsStr::new(inner);
Self { inner, utf8 }
}
/// Does the argument look like a stdio argument (`-`)
pub fn is_stdio(&self) -> bool {
self.inner.as_ref() == "-"
}
/// Does the argument look like an argument escape (`--`)
pub fn is_escape(&self) -> bool {
self.inner.as_ref() == "--"
}
/// Does the argument look like a number
pub fn is_number(&self) -> bool {
self.to_value()
.map(|s| s.parse::<f64>().is_ok())
.unwrap_or_default()
}
/// Treat as a long-flag
///
/// **NOTE:** May return an empty flag. Check [`ParsedArg::is_escape`] to separately detect `--`.
///
/// **NOTE:** Will not match [`ParsedArg::is_stdio`], completion engines will need to check
/// that case.
pub fn to_long(&self) -> Option<(Result<&str, &RawOsStr>, Option<&RawOsStr>)> {
if let Some(raw) = self.utf8 {
let remainder = raw.strip_prefix("--")?;
let (flag, value) = if let Some((p0, p1)) = remainder.split_once('=') {
(p0, Some(p1))
} else {
(remainder, None)
};
let flag = Ok(flag);
let value = value.map(RawOsStr::from_str);
Some((flag, value))
} else {
let raw = self.inner.as_ref();
let remainder = raw.strip_prefix("--")?;
let (flag, value) = if let Some((p0, p1)) = remainder.split_once('=') {
(p0, Some(p1))
} else {
(remainder, None)
};
let flag = flag.to_str().ok_or(flag);
Some((flag, value))
}
}
/// Can treat as a long-flag
///
/// **NOTE:** May return an empty flag. Check [`ParsedArg::is_escape`] to separately detect `--`.
pub fn is_long(&self) -> bool {
self.inner.as_ref().starts_with("--")
}
/// Treat as a short-flag
///
/// **NOTE:** Maybe return an empty flag. Check [`ParsedArg::is_stdio`] to separately detect
/// `-`.
pub fn to_short(&self) -> Option<ShortFlags<'_>> {
if let Some(remainder_os) = self.inner.as_ref().strip_prefix('-') {
if remainder_os.starts_with('-') {
None
} else {
let remainder = self.utf8.map(|s| &s[1..]);
Some(ShortFlags::new(remainder_os, remainder))
}
} else {
None
}
}
/// Can treat as a short-flag
///
/// **NOTE:** Maybe return an empty flag. Check [`ParsedArg::is_stdio`] to separately detect
/// `-`.
pub fn is_short(&self) -> bool {
self.inner.as_ref().starts_with('-') && !self.is_long()
}
/// Treat as a value
///
/// **NOTE:** May return a flag or an escape.
pub fn to_value_os(&self) -> &RawOsStr {
self.inner.as_ref()
}
/// Treat as a value
///
/// **NOTE:** May return a flag or an escape.
pub fn to_value(&self) -> Result<&str, &RawOsStr> {
self.utf8.ok_or_else(|| self.inner.as_ref())
}
/// Safely print an argument that may contain non-UTF8 content
///
/// This may perform lossy conversion, depending on the platform. If you would like an implementation which escapes the path please use Debug instead.
pub fn display(&self) -> impl std::fmt::Display + '_ {
self.inner.to_str_lossy()
}
}
/// Walk through short flags within a [`ParsedArg`]
#[derive(Clone, Debug)]
pub struct ShortFlags<'s> {
inner: &'s RawOsStr,
utf8_prefix: std::str::CharIndices<'s>,
invalid_suffix: Option<&'s RawOsStr>,
}
impl<'s> ShortFlags<'s> {
fn new(inner: &'s RawOsStr, utf8: Option<&'s str>) -> Self {
let (utf8_prefix, invalid_suffix) = if let Some(utf8) = utf8 {
(utf8, None)
} else {
split_nonutf8_once(inner)
};
let utf8_prefix = utf8_prefix.char_indices();
Self {
inner,
utf8_prefix,
invalid_suffix,
}
}
/// Move the iterator forward by `n` short flags
pub fn advance_by(&mut self, n: usize) -> Result<(), usize> {
for i in 0..n {
self.next().ok_or(i)?.map_err(|_| i)?;
}
Ok(())
}
/// No short flags left
pub fn is_empty(&self) -> bool {
self.invalid_suffix.is_none() && self.utf8_prefix.as_str().is_empty()
}
/// Does the short flag look like a number
///
/// Ideally call this before doing any iterator
pub fn is_number(&self) -> bool {
self.invalid_suffix.is_none() && self.utf8_prefix.as_str().parse::<f64>().is_ok()
}
/// Advance the iterator, returning the next short flag on success
///
/// On error, returns the invalid-UTF8 value
pub fn next_flag(&mut self) -> Option<Result<char, &'s RawOsStr>> {
if let Some((_, flag)) = self.utf8_prefix.next() {
return Some(Ok(flag));
}
if let Some(suffix) = self.invalid_suffix {
self.invalid_suffix = None;
return Some(Err(suffix));
}
None
}
/// Advance the iterator, returning everything left as a value
pub fn next_value_os(&mut self) -> Option<&'s RawOsStr> {
if let Some((index, _)) = self.utf8_prefix.next() {
self.utf8_prefix = "".char_indices();
self.invalid_suffix = None;
return Some(&self.inner[index..]);
}
if let Some(suffix) = self.invalid_suffix {
self.invalid_suffix = None;
return Some(suffix);
}
None
}
}
impl<'s> Iterator for ShortFlags<'s> {
type Item = Result<char, &'s RawOsStr>;
fn next(&mut self) -> Option<Self::Item> {
self.next_flag()
}
}
fn split_nonutf8_once(b: &RawOsStr) -> (&str, Option<&RawOsStr>) {
match std::str::from_utf8(b.as_raw_bytes()) {
Ok(s) => (s, None),
Err(err) => {
let (valid, after_valid) = b.split_at(err.valid_up_to());
let valid = std::str::from_utf8(valid.as_raw_bytes()).unwrap();
(valid, Some(after_valid))
}
}
}

21
clap_lex/tests/lexer.rs Normal file
View file

@ -0,0 +1,21 @@
#[test]
fn insert() {
let mut raw = clap_lex::RawArgs::new(["bin", "a", "b", "c"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("a")));
raw.insert(&cursor, &["1", "2", "3"]);
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("1")));
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("2")));
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("3")));
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("b")));
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("c")));
let mut cursor = raw.cursor();
let rest = raw
.remaining(&mut cursor)
.map(|s| s.to_string_lossy())
.collect::<Vec<_>>();
assert_eq!(rest, vec!["bin", "a", "1", "2", "3", "b", "c"]);
}

190
clap_lex/tests/parsed.rs Normal file
View file

@ -0,0 +1,190 @@
// Despite our design philosophy being to support completion generation, we aren't considering `-`
// the start of a long because there is no valid value to return.
#[test]
fn to_long_stdio() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(!next.is_long());
assert_eq!(next.to_long(), None);
}
#[test]
fn to_long_escape() {
let raw = clap_lex::RawArgs::new(["bin", "--"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_long());
let (key, value) = next.to_long().unwrap();
assert_eq!(key, Ok(""));
assert_eq!(value, None);
}
#[test]
fn to_long_no_value() {
let raw = clap_lex::RawArgs::new(["bin", "--long"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_long());
let (key, value) = next.to_long().unwrap();
assert_eq!(key, Ok("long"));
assert_eq!(value, None);
}
#[test]
fn to_long_with_empty_value() {
let raw = clap_lex::RawArgs::new(["bin", "--long="]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_long());
let (key, value) = next.to_long().unwrap();
assert_eq!(key, Ok("long"));
assert_eq!(value, Some(clap_lex::RawOsStr::from_str("")));
}
#[test]
fn to_long_with_value() {
let raw = clap_lex::RawArgs::new(["bin", "--long=hello"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_long());
let (key, value) = next.to_long().unwrap();
assert_eq!(key, Ok("long"));
assert_eq!(value, Some(clap_lex::RawOsStr::from_str("hello")));
}
#[test]
fn to_short_stdio() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_short());
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.next_value_os(), None);
}
#[test]
fn to_short_escape() {
let raw = clap_lex::RawArgs::new(["bin", "--"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(!next.is_short());
assert!(next.to_short().is_none());
}
#[test]
fn to_short_long() {
let raw = clap_lex::RawArgs::new(["bin", "--long"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(!next.is_short());
assert!(next.to_short().is_none());
}
#[test]
fn to_short() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_short());
let shorts = next.to_short().unwrap();
let actual: String = shorts.map(|s| s.unwrap()).collect();
assert_eq!(actual, "short");
}
#[test]
fn is_negative_number() {
let raw = clap_lex::RawArgs::new(["bin", "-10.0"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_number());
}
#[test]
fn is_positive_number() {
let raw = clap_lex::RawArgs::new(["bin", "10.0"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_number());
}
#[test]
fn is_not_number() {
let raw = clap_lex::RawArgs::new(["bin", "--10.0"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(!next.is_number());
}
#[test]
fn is_stdio() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_stdio());
}
#[test]
fn is_not_stdio() {
let raw = clap_lex::RawArgs::new(["bin", "--"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(!next.is_stdio());
}
#[test]
fn is_escape() {
let raw = clap_lex::RawArgs::new(["bin", "--"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(next.is_escape());
}
#[test]
fn is_not_escape() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
assert!(!next.is_escape());
}

198
clap_lex/tests/shorts.rs Normal file
View file

@ -0,0 +1,198 @@
#[test]
fn iter() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let shorts = next.to_short().unwrap();
let actual: String = shorts.map(|s| s.unwrap()).collect();
assert_eq!(actual, "short");
}
#[test]
fn next_flag() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
let mut actual = String::new();
actual.push(shorts.next_flag().unwrap().unwrap());
actual.push(shorts.next_flag().unwrap().unwrap());
actual.push(shorts.next_flag().unwrap().unwrap());
actual.push(shorts.next_flag().unwrap().unwrap());
actual.push(shorts.next_flag().unwrap().unwrap());
assert_eq!(shorts.next_flag(), None);
assert_eq!(actual, "short");
}
#[test]
fn next_value_os() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
let actual = shorts.next_value_os().unwrap().to_str_lossy();
assert_eq!(actual, "short");
}
#[test]
fn next_flag_with_value() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.next_flag().unwrap().unwrap(), 's');
let actual = shorts.next_value_os().unwrap().to_str_lossy();
assert_eq!(actual, "hort");
}
#[test]
fn next_flag_with_no_value() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.next_flag().unwrap().unwrap(), 's');
assert_eq!(shorts.next_flag().unwrap().unwrap(), 'h');
assert_eq!(shorts.next_flag().unwrap().unwrap(), 'o');
assert_eq!(shorts.next_flag().unwrap().unwrap(), 'r');
assert_eq!(shorts.next_flag().unwrap().unwrap(), 't');
assert_eq!(shorts.next_value_os(), None);
}
#[test]
fn advance_by_nothing() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.advance_by(0), Ok(()));
let actual: String = shorts.map(|s| s.unwrap()).collect();
assert_eq!(actual, "short");
}
#[test]
fn advance_by_nothing_with_nothing() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.advance_by(0), Ok(()));
let actual: String = shorts.map(|s| s.unwrap()).collect();
assert_eq!(actual, "");
}
#[test]
fn advance_by_something() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.advance_by(2), Ok(()));
let actual: String = shorts.map(|s| s.unwrap()).collect();
assert_eq!(actual, "ort");
}
#[test]
fn advance_by_out_of_bounds() {
let raw = clap_lex::RawArgs::new(["bin", "-short"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
assert_eq!(shorts.advance_by(2000), Err(5));
let actual: String = shorts.map(|s| s.unwrap()).collect();
assert_eq!(actual, "");
}
#[test]
fn is_empty() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let shorts = next.to_short().unwrap();
assert!(shorts.is_empty());
}
#[test]
fn is_not_empty() {
let raw = clap_lex::RawArgs::new(["bin", "-hello"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let shorts = next.to_short().unwrap();
assert!(!shorts.is_empty());
}
#[test]
fn is_partial_not_empty() {
let raw = clap_lex::RawArgs::new(["bin", "-hello"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
shorts.advance_by(1).unwrap();
assert!(!shorts.is_empty());
}
#[test]
fn is_exhausted_empty() {
let raw = clap_lex::RawArgs::new(["bin", "-"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let mut shorts = next.to_short().unwrap();
shorts.advance_by(20000).unwrap_err();
assert!(shorts.is_empty());
}
#[test]
fn is_number() {
let raw = clap_lex::RawArgs::new(["bin", "-1.0"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let shorts = next.to_short().unwrap();
assert!(shorts.is_number());
}
#[test]
fn is_not_number() {
let raw = clap_lex::RawArgs::new(["bin", "-hello"]);
let mut cursor = raw.cursor();
assert_eq!(raw.next_os(&mut cursor), Some(std::ffi::OsStr::new("bin")));
let next = raw.next(&mut cursor).unwrap();
let shorts = next.to_short().unwrap();
assert!(!shorts.is_number());
}

View file

@ -3,7 +3,6 @@
// Std // Std
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::ffi::OsStr;
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt; use std::fmt;
use std::io; use std::io;
@ -11,7 +10,6 @@ use std::ops::Index;
use std::path::Path; use std::path::Path;
// Third Party // Third Party
use os_str_bytes::RawOsStr;
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
use yaml_rust::Yaml; use yaml_rust::Yaml;
@ -23,7 +21,7 @@ use crate::error::ErrorKind;
use crate::error::Result as ClapResult; use crate::error::Result as ClapResult;
use crate::mkeymap::MKeyMap; use crate::mkeymap::MKeyMap;
use crate::output::{fmt::Colorizer, Help, HelpWriter, Usage}; use crate::output::{fmt::Colorizer, Help, HelpWriter, Usage};
use crate::parse::{ArgMatcher, ArgMatches, Input, Parser}; use crate::parse::{ArgMatcher, ArgMatches, Parser};
use crate::util::ChildGraph; use crate::util::ChildGraph;
use crate::util::{color::ColorChoice, Id, Key}; use crate::util::{color::ColorChoice, Id, Key};
use crate::{Error, INTERNAL_ERROR_MSG}; use crate::{Error, INTERNAL_ERROR_MSG};
@ -101,7 +99,7 @@ pub struct App<'help> {
g_settings: AppFlags, g_settings: AppFlags,
args: MKeyMap<'help>, args: MKeyMap<'help>,
subcommands: Vec<App<'help>>, subcommands: Vec<App<'help>>,
replacers: HashMap<&'help OsStr, &'help [&'help str]>, replacers: HashMap<&'help str, &'help [&'help str]>,
groups: Vec<ArgGroup<'help>>, groups: Vec<ArgGroup<'help>>,
current_help_heading: Option<&'help str>, current_help_heading: Option<&'help str>,
current_disp_ord: Option<usize>, current_disp_ord: Option<usize>,
@ -633,11 +631,12 @@ impl<'help> App<'help> {
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,
T: Into<OsString> + Clone, T: Into<OsString> + Clone,
{ {
let mut it = Input::from(itr.into_iter()); let mut raw_args = clap_lex::RawArgs::new(itr.into_iter());
let mut cursor = raw_args.cursor();
#[cfg(feature = "unstable-multicall")] #[cfg(feature = "unstable-multicall")]
if self.settings.is_set(AppSettings::Multicall) { if self.settings.is_set(AppSettings::Multicall) {
if let Some((argv0, _)) = it.next() { if let Some(argv0) = raw_args.next_os(&mut cursor) {
let argv0 = Path::new(&argv0); let argv0 = Path::new(&argv0);
if let Some(command) = argv0.file_stem().and_then(|f| f.to_str()) { if let Some(command) = argv0.file_stem().and_then(|f| f.to_str()) {
// Stop borrowing command so we can get another mut ref to it. // Stop borrowing command so we can get another mut ref to it.
@ -648,11 +647,11 @@ impl<'help> App<'help> {
); );
debug!("App::try_get_matches_from_mut: Reinserting command into arguments so subcommand parser matches it"); debug!("App::try_get_matches_from_mut: Reinserting command into arguments so subcommand parser matches it");
it.insert(&[&command]); raw_args.insert(&cursor, &[&command]);
debug!("App::try_get_matches_from_mut: Clearing name and bin_name so that displayed command name starts with applet name"); debug!("App::try_get_matches_from_mut: Clearing name and bin_name so that displayed command name starts with applet name");
self.name.clear(); self.name.clear();
self.bin_name = None; self.bin_name = None;
return self._do_parse(&mut it); return self._do_parse(&mut raw_args, cursor);
} }
} }
}; };
@ -665,7 +664,7 @@ impl<'help> App<'help> {
// to display // to display
// the full path when displaying help messages and such // the full path when displaying help messages and such
if !self.settings.is_set(AppSettings::NoBinaryName) { if !self.settings.is_set(AppSettings::NoBinaryName) {
if let Some((name, _)) = it.next() { if let Some(name) = raw_args.next_os(&mut cursor) {
let p = Path::new(name); let p = Path::new(name);
if let Some(f) = p.file_name() { if let Some(f) = p.file_name() {
@ -678,7 +677,7 @@ impl<'help> App<'help> {
} }
} }
self._do_parse(&mut it) self._do_parse(&mut raw_args, cursor)
} }
/// Prints the short help message (`-h`) to [`io::stdout()`]. /// Prints the short help message (`-h`) to [`io::stdout()`].
@ -1944,7 +1943,7 @@ impl<'help> App<'help> {
#[cfg(feature = "unstable-replace")] #[cfg(feature = "unstable-replace")]
#[must_use] #[must_use]
pub fn replace(mut self, name: &'help str, target: &'help [&'help str]) -> Self { pub fn replace(mut self, name: &'help str, target: &'help [&'help str]) -> Self {
self.replacers.insert(OsStr::new(name), target); self.replacers.insert(name, target);
self self
} }
@ -3932,7 +3931,7 @@ impl<'help> App<'help> {
self.max_w self.max_w
} }
pub(crate) fn get_replacement(&self, key: &OsStr) -> Option<&[&str]> { pub(crate) fn get_replacement(&self, key: &str) -> Option<&[&str]> {
self.replacers.get(key).copied() self.replacers.get(key).copied()
} }
@ -3954,7 +3953,11 @@ impl<'help> App<'help> {
} }
} }
fn _do_parse(&mut self, it: &mut Input) -> ClapResult<ArgMatches> { fn _do_parse(
&mut self,
raw_args: &mut clap_lex::RawArgs,
args_cursor: clap_lex::ArgCursor,
) -> ClapResult<ArgMatches> {
debug!("App::_do_parse"); debug!("App::_do_parse");
// If there are global arguments, or settings we need to propagate them down to subcommands // If there are global arguments, or settings we need to propagate them down to subcommands
@ -3965,7 +3968,7 @@ impl<'help> App<'help> {
// do the real parsing // do the real parsing
let mut parser = Parser::new(self); let mut parser = Parser::new(self);
if let Err(error) = parser.get_matches_with(&mut matcher, it) { if let Err(error) = parser.get_matches_with(&mut matcher, raw_args, args_cursor) {
if self.is_set(AppSettings::IgnoreErrors) { if self.is_set(AppSettings::IgnoreErrors) {
debug!("App::_do_parse: ignoring error: {}", error); debug!("App::_do_parse: ignoring error: {}", error);
} else { } else {
@ -4651,7 +4654,7 @@ impl<'help> App<'help> {
} }
/// Find a flag subcommand name by long flag or an alias /// Find a flag subcommand name by long flag or an alias
pub(crate) fn find_long_subcmd(&self, long: &RawOsStr) -> Option<&str> { pub(crate) fn find_long_subcmd(&self, long: &str) -> Option<&str> {
self.get_subcommands() self.get_subcommands()
.find(|sc| sc.long_flag_aliases_to(long)) .find(|sc| sc.long_flag_aliases_to(long))
.map(|sc| sc.get_name()) .map(|sc| sc.get_name())

View file

@ -1,6 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use os_str_bytes::RawOsStr; use clap_lex::RawOsStr;
use crate::build::arg::ArgProvider; use crate::build::arg::ArgProvider;
use crate::mkeymap::KeyType; use crate::mkeymap::KeyType;

View file

@ -49,6 +49,15 @@ impl PartialEq<&str> for KeyType {
} }
} }
impl PartialEq<str> for KeyType {
fn eq(&self, rhs: &str) -> bool {
match self {
KeyType::Long(l) => l == rhs,
_ => false,
}
}
}
impl PartialEq<OsStr> for KeyType { impl PartialEq<OsStr> for KeyType {
fn eq(&self, rhs: &OsStr) -> bool { fn eq(&self, rhs: &OsStr) -> bool {
match self { match self {

View file

@ -1,13 +1,13 @@
pub mod features;
mod arg_matcher; mod arg_matcher;
pub mod matches;
mod parser; mod parser;
mod validator; mod validator;
pub mod features;
pub mod matches;
pub(crate) use self::arg_matcher::ArgMatcher; pub(crate) use self::arg_matcher::ArgMatcher;
pub(crate) use self::matches::{MatchedArg, SubCommand}; pub(crate) use self::matches::{MatchedArg, SubCommand};
pub(crate) use self::parser::{Input, ParseState, Parser}; pub(crate) use self::parser::{ParseState, Parser};
pub(crate) use self::validator::Validator; pub(crate) use self::validator::Validator;
pub use self::matches::{ArgMatches, Indices, OsValues, ValueSource, Values}; pub use self::matches::{ArgMatches, Indices, OsValues, ValueSource, Values};

View file

@ -5,7 +5,7 @@ use std::{
}; };
// Third Party // Third Party
use os_str_bytes::RawOsStr; use clap_lex::RawOsStr;
// Internal // Internal
use crate::build::{Arg, Command}; use crate::build::{Arg, Command};
@ -63,7 +63,8 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
pub(crate) fn get_matches_with( pub(crate) fn get_matches_with(
&mut self, &mut self,
matcher: &mut ArgMatcher, matcher: &mut ArgMatcher,
it: &mut Input, raw_args: &mut clap_lex::RawArgs,
mut args_cursor: clap_lex::ArgCursor,
) -> ClapResult<()> { ) -> ClapResult<()> {
debug!("Parser::get_matches_with"); debug!("Parser::get_matches_with");
// Verify all positional assertions pass // Verify all positional assertions pass
@ -88,22 +89,25 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// If any arg sets .last(true) // If any arg sets .last(true)
let contains_last = self.cmd.get_arguments().any(|x| x.is_last_set()); let contains_last = self.cmd.get_arguments().any(|x| x.is_last_set());
while let Some((arg_os, remaining_args)) = it.next() { while let Some(arg_os) = raw_args.next(&mut args_cursor) {
// Recover the replaced items if any. // Recover the replaced items if any.
if let Some(replaced_items) = self.cmd.get_replacement(arg_os) { if let Some(replaced_items) = arg_os
.to_value()
.ok()
.and_then(|a| self.cmd.get_replacement(a))
{
debug!( debug!(
"Parser::get_matches_with: found replacer: {:?}, target: {:?}", "Parser::get_matches_with: found replacer: {:?}, target: {:?}",
arg_os, replaced_items arg_os, replaced_items
); );
it.insert(replaced_items); raw_args.insert(&args_cursor, replaced_items);
continue; continue;
} }
let arg_os = RawOsStr::new(arg_os);
debug!( debug!(
"Parser::get_matches_with: Begin parsing '{:?}' ({:?})", "Parser::get_matches_with: Begin parsing '{:?}' ({:?})",
arg_os, arg_os.to_value_os(),
arg_os.as_raw_bytes() arg_os.to_value_os().as_raw_bytes()
); );
// Correct pos_counter. // Correct pos_counter.
@ -137,7 +141,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
); );
if low_index_mults || missing_pos { if low_index_mults || missing_pos {
let skip_current = if let Some(n) = remaining_args.get(0) { let skip_current = if let Some(n) = raw_args.peek(&args_cursor) {
if let Some(p) = self if let Some(p) = self
.cmd .cmd
.get_positionals() .get_positionals()
@ -148,9 +152,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// pos_counter(which means current value cannot be a // pos_counter(which means current value cannot be a
// positional argument with a value next to it), assume // positional argument with a value next to it), assume
// current value matches the next arg. // current value matches the next arg.
let n = RawOsStr::new(n);
self.is_new_arg(&n, p) self.is_new_arg(&n, p)
|| self.possible_subcommand(&n, valid_arg_found).is_some() || self
.possible_subcommand(n.to_value(), valid_arg_found)
.is_some()
} else { } else {
true true
} }
@ -182,24 +187,25 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
|| !matches!(parse_state, ParseState::Opt(_) | ParseState::Pos(_)) || !matches!(parse_state, ParseState::Opt(_) | ParseState::Pos(_))
{ {
// Does the arg match a subcommand name, or any of its aliases (if defined) // Does the arg match a subcommand name, or any of its aliases (if defined)
let sc_name = self.possible_subcommand(&arg_os, valid_arg_found); let sc_name = self.possible_subcommand(arg_os.to_value(), valid_arg_found);
debug!("Parser::get_matches_with: sc={:?}", sc_name); debug!("Parser::get_matches_with: sc={:?}", sc_name);
if let Some(sc_name) = sc_name { if let Some(sc_name) = sc_name {
if sc_name == "help" if sc_name == "help"
&& !self.is_set(AS::NoAutoHelp) && !self.is_set(AS::NoAutoHelp)
&& !self.cmd.is_disable_help_subcommand_set() && !self.cmd.is_disable_help_subcommand_set()
{ {
self.parse_help_subcommand(remaining_args)?; self.parse_help_subcommand(raw_args.remaining(&mut args_cursor))?;
} }
subcmd_name = Some(sc_name.to_owned()); subcmd_name = Some(sc_name.to_owned());
break; break;
} }
} }
if let Some(long_arg) = arg_os.strip_prefix("--") { if let Some((long_arg, long_value)) = arg_os.to_long() {
let parse_result = self.parse_long_arg( let parse_result = self.parse_long_arg(
matcher, matcher,
long_arg, long_arg,
long_value,
&parse_state, &parse_state,
&mut valid_arg_found, &mut valid_arg_found,
trailing_values, trailing_values,
@ -238,8 +244,8 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
)); ));
} }
ParseResult::NoMatchingArg { arg } => { ParseResult::NoMatchingArg { arg } => {
let remaining_args: Vec<_> = remaining_args let remaining_args: Vec<_> = raw_args
.iter() .remaining(&mut args_cursor)
.map(|x| x.to_str().expect(INVALID_UTF8)) .map(|x| x.to_str().expect(INVALID_UTF8))
.collect(); .collect();
return Err(self.did_you_mean_error(&arg, matcher, &remaining_args)); return Err(self.did_you_mean_error(&arg, matcher, &remaining_args));
@ -265,7 +271,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
unreachable!() unreachable!()
} }
} }
} else if let Some(short_arg) = arg_os.strip_prefix("-") { } else if let Some(short_arg) = arg_os.to_short() {
// Arg looks like a short flag, and not a possible number // Arg looks like a short flag, and not a possible number
// Try to parse short args like normal, if allow_hyphen_values or // Try to parse short args like normal, if allow_hyphen_values or
@ -302,7 +308,8 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
keep_state = self keep_state = self
.flag_subcmd_at .flag_subcmd_at
.map(|at| { .map(|at| {
it.cursor -= 1; raw_args
.seek(&mut args_cursor, clap_lex::SeekFrom::Current(-1));
// Since we are now saving the current state, the number of flags to skip during state recovery should // Since we are now saving the current state, the number of flags to skip during state recovery should
// be the current index (`cur_idx`) minus ONE UNIT TO THE LEFT of the starting position. // be the current index (`cur_idx`) minus ONE UNIT TO THE LEFT of the starting position.
self.flag_subcmd_skip = self.cur_idx.get() - at + 1; self.flag_subcmd_skip = self.cur_idx.get() - at + 1;
@ -354,7 +361,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// get the option so we can check the settings // get the option so we can check the settings
let parse_result = self.add_val_to_arg( let parse_result = self.add_val_to_arg(
&self.cmd[id], &self.cmd[id],
&arg_os, arg_os.to_value_os(),
matcher, matcher,
ValueSource::CommandLine, ValueSource::CommandLine,
true, true,
@ -374,7 +381,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
if p.is_last_set() && !trailing_values { if p.is_last_set() && !trailing_values {
return Err(ClapError::unknown_argument( return Err(ClapError::unknown_argument(
self.cmd, self.cmd,
arg_os.to_str_lossy().into_owned(), arg_os.display().to_string(),
None, None,
Usage::new(self.cmd).create_usage_with_title(&[]), Usage::new(self.cmd).create_usage_with_title(&[]),
)); ));
@ -395,7 +402,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
let append = self.has_val_groups(matcher, p); let append = self.has_val_groups(matcher, p);
self.add_val_to_arg( self.add_val_to_arg(
p, p,
&arg_os, arg_os.to_value_os(),
matcher, matcher,
ValueSource::CommandLine, ValueSource::CommandLine,
append, append,
@ -412,9 +419,9 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
valid_arg_found = true; valid_arg_found = true;
} else if self.cmd.is_allow_external_subcommands_set() { } else if self.cmd.is_allow_external_subcommands_set() {
// Get external subcommand name // Get external subcommand name
let sc_name = match arg_os.to_str() { let sc_name = match arg_os.to_value() {
Some(s) => s.to_string(), Ok(s) => s.to_string(),
None => { Err(_) => {
return Err(ClapError::invalid_utf8( return Err(ClapError::invalid_utf8(
self.cmd, self.cmd,
Usage::new(self.cmd).create_usage_with_title(&[]), Usage::new(self.cmd).create_usage_with_title(&[]),
@ -425,7 +432,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// Collect the external subcommand args // Collect the external subcommand args
let mut sc_m = ArgMatcher::new(self.cmd); let mut sc_m = ArgMatcher::new(self.cmd);
while let Some((v, _)) = it.next() { for v in raw_args.remaining(&mut args_cursor) {
let allow_invalid_utf8 = self let allow_invalid_utf8 = self
.cmd .cmd
.is_allow_invalid_utf8_for_external_subcommands_set(); .is_allow_invalid_utf8_for_external_subcommands_set();
@ -466,7 +473,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
.expect(INTERNAL_ERROR_MSG) .expect(INTERNAL_ERROR_MSG)
.get_name() .get_name()
.to_owned(); .to_owned();
self.parse_subcommand(&sc_name, matcher, it, keep_state)?; self.parse_subcommand(&sc_name, matcher, raw_args, args_cursor, keep_state)?;
} }
Validator::new(self).validate(parse_state, matcher, trailing_values) Validator::new(self).validate(parse_state, matcher, trailing_values)
@ -474,23 +481,28 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
fn match_arg_error( fn match_arg_error(
&self, &self,
arg_os: &RawOsStr, arg_os: &clap_lex::ParsedArg<'_>,
valid_arg_found: bool, valid_arg_found: bool,
trailing_values: bool, trailing_values: bool,
) -> ClapError { ) -> ClapError {
// If argument follows a `--` // If argument follows a `--`
if trailing_values { if trailing_values {
// If the arg matches a subcommand name, or any of its aliases (if defined) // If the arg matches a subcommand name, or any of its aliases (if defined)
if self.possible_subcommand(arg_os, valid_arg_found).is_some() { if self
.possible_subcommand(arg_os.to_value(), valid_arg_found)
.is_some()
{
return ClapError::unnecessary_double_dash( return ClapError::unnecessary_double_dash(
self.cmd, self.cmd,
arg_os.to_str_lossy().into_owned(), arg_os.display().to_string(),
Usage::new(self.cmd).create_usage_with_title(&[]), Usage::new(self.cmd).create_usage_with_title(&[]),
); );
} }
} }
let candidates = let candidates = suggestions::did_you_mean(
suggestions::did_you_mean(&arg_os.to_str_lossy(), self.cmd.all_subcommand_names()); &arg_os.display().to_string(),
self.cmd.all_subcommand_names(),
);
// If the argument looks like a subcommand. // If the argument looks like a subcommand.
if !candidates.is_empty() { if !candidates.is_empty() {
let candidates: Vec<_> = candidates let candidates: Vec<_> = candidates
@ -499,7 +511,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
.collect(); .collect();
return ClapError::invalid_subcommand( return ClapError::invalid_subcommand(
self.cmd, self.cmd,
arg_os.to_str_lossy().into_owned(), arg_os.display().to_string(),
candidates.join(" or "), candidates.join(" or "),
self.cmd self.cmd
.get_bin_name() .get_bin_name()
@ -513,7 +525,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
{ {
return ClapError::unrecognized_subcommand( return ClapError::unrecognized_subcommand(
self.cmd, self.cmd,
arg_os.to_str_lossy().into_owned(), arg_os.display().to_string(),
self.cmd self.cmd
.get_bin_name() .get_bin_name()
.unwrap_or_else(|| self.cmd.get_name()) .unwrap_or_else(|| self.cmd.get_name())
@ -522,15 +534,20 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
} }
ClapError::unknown_argument( ClapError::unknown_argument(
self.cmd, self.cmd,
arg_os.to_str_lossy().into_owned(), arg_os.display().to_string(),
None, None,
Usage::new(self.cmd).create_usage_with_title(&[]), Usage::new(self.cmd).create_usage_with_title(&[]),
) )
} }
// Checks if the arg matches a subcommand name, or any of its aliases (if defined) // Checks if the arg matches a subcommand name, or any of its aliases (if defined)
fn possible_subcommand(&self, arg_os: &RawOsStr, valid_arg_found: bool) -> Option<&str> { fn possible_subcommand(
debug!("Parser::possible_subcommand: arg={:?}", arg_os); &self,
arg: Result<&str, &RawOsStr>,
valid_arg_found: bool,
) -> Option<&str> {
debug!("Parser::possible_subcommand: arg={:?}", arg);
let arg = arg.ok()?;
if !(self.cmd.is_args_conflicts_with_subcommands_set() && valid_arg_found) { if !(self.cmd.is_args_conflicts_with_subcommands_set() && valid_arg_found) {
if self.cmd.is_infer_subcommands_set() { if self.cmd.is_infer_subcommands_set() {
@ -539,7 +556,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
let v = self let v = self
.cmd .cmd
.all_subcommand_names() .all_subcommand_names()
.filter(|s| RawOsStr::from_str(s).starts_with_os(arg_os)) .filter(|s| s.starts_with(arg))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if v.len() == 1 { if v.len() == 1 {
@ -549,7 +566,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// If there is any ambiguity, fallback to non-infer subcommand // If there is any ambiguity, fallback to non-infer subcommand
// search. // search.
} }
if let Some(sc) = self.cmd.find_subcommand(arg_os) { if let Some(sc) = self.cmd.find_subcommand(arg) {
return Some(sc.get_name()); return Some(sc.get_name());
} }
} }
@ -557,21 +574,18 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
} }
// Checks if the arg matches a long flag subcommand name, or any of its aliases (if defined) // Checks if the arg matches a long flag subcommand name, or any of its aliases (if defined)
fn possible_long_flag_subcommand(&self, arg_os: &RawOsStr) -> Option<&str> { fn possible_long_flag_subcommand(&self, arg: &str) -> Option<&str> {
debug!("Parser::possible_long_flag_subcommand: arg={:?}", arg_os); debug!("Parser::possible_long_flag_subcommand: arg={:?}", arg);
if self.cmd.is_infer_subcommands_set() { if self.cmd.is_infer_subcommands_set() {
let options = self let options = self
.cmd .cmd
.get_subcommands() .get_subcommands()
.fold(Vec::new(), |mut options, sc| { .fold(Vec::new(), |mut options, sc| {
if let Some(long) = sc.get_long_flag() { if let Some(long) = sc.get_long_flag() {
if RawOsStr::from_str(long).starts_with_os(arg_os) { if long.starts_with(arg) {
options.push(long); options.push(long);
} }
options.extend( options.extend(sc.get_all_aliases().filter(|alias| alias.starts_with(arg)))
sc.get_all_aliases()
.filter(|alias| RawOsStr::from_str(alias).starts_with_os(arg_os)),
)
} }
options options
}); });
@ -580,17 +594,20 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
} }
for sc in options { for sc in options {
if sc == arg_os { if sc == arg {
return Some(sc); return Some(sc);
} }
} }
} else if let Some(sc_name) = self.cmd.find_long_subcmd(arg_os) { } else if let Some(sc_name) = self.cmd.find_long_subcmd(arg) {
return Some(sc_name); return Some(sc_name);
} }
None None
} }
fn parse_help_subcommand(&self, cmds: &[OsString]) -> ClapResult<ParseResult> { fn parse_help_subcommand(
&self,
cmds: impl Iterator<Item = &'cmd OsStr>,
) -> ClapResult<ParseResult> {
debug!("Parser::parse_help_subcommand"); debug!("Parser::parse_help_subcommand");
let mut bin_name = self let mut bin_name = self
@ -602,7 +619,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
let mut sc = { let mut sc = {
let mut sc = self.cmd.clone(); let mut sc = self.cmd.clone();
for cmd in cmds.iter() { for cmd in cmds {
sc = if let Some(c) = sc.find_subcommand(cmd) { sc = if let Some(c) = sc.find_subcommand(cmd) {
c c
} else if let Some(c) = sc.find_subcommand(&cmd.to_string_lossy()) { } else if let Some(c) = sc.find_subcommand(&cmd.to_string_lossy()) {
@ -633,32 +650,42 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
Err(parser.help_err(true)) Err(parser.help_err(true))
} }
fn is_new_arg(&self, next: &RawOsStr, current_positional: &Arg) -> bool { fn is_new_arg(&self, next: &clap_lex::ParsedArg<'_>, current_positional: &Arg) -> bool {
#![allow(clippy::needless_bool)] // Prefer consistent if/else-if ladder
debug!( debug!(
"Parser::is_new_arg: {:?}:{:?}", "Parser::is_new_arg: {:?}:{:?}",
next, current_positional.name next.to_value_os(),
current_positional.name
); );
if self.cmd.is_allow_hyphen_values_set() if self.cmd.is_allow_hyphen_values_set()
|| self.cmd[&current_positional.id].is_allow_hyphen_values_set() || self.cmd[&current_positional.id].is_allow_hyphen_values_set()
|| (self.cmd.is_allow_negative_numbers_set() || (self.cmd.is_allow_negative_numbers_set() && next.is_number())
&& next.to_str_lossy().parse::<f64>().is_ok())
{ {
// If allow hyphen, this isn't a new arg. // If allow hyphen, this isn't a new arg.
debug!("Parser::is_new_arg: Allow hyphen"); debug!("Parser::is_new_arg: Allow hyphen");
false false
} else if next.starts_with("--") { } else if next.is_escape() {
// If this is a long flag, this is a new arg. // Ensure we don't assuming escapes are long args
debug!("Parser::is_new_arg: -- found"); debug!("Parser::is_new_arg: -- found");
true false
} else if next.starts_with("-") { } else if next.is_stdio() {
// Ensure we don't assume stdio is a short arg
debug!("Parser::is_new_arg: - found"); debug!("Parser::is_new_arg: - found");
false
} else if next.is_long() {
// If this is a long flag, this is a new arg.
debug!("Parser::is_new_arg: --<something> found");
true
} else if next.is_short() {
// If this is a short flag, this is a new arg. But a singe '-' by // If this is a short flag, this is a new arg. But a singe '-' by
// itself is a value and typically means "stdin" on unix systems. // itself is a value and typically means "stdin" on unix systems.
next.raw_len() != 1 debug!("Parser::is_new_arg: -<something> found");
true
} else { } else {
debug!("Parser::is_new_arg: value");
// Nothing special, this is a value. // Nothing special, this is a value.
debug!("Parser::is_new_arg: value");
false false
} }
} }
@ -667,7 +694,8 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
&mut self, &mut self,
sc_name: &str, sc_name: &str,
matcher: &mut ArgMatcher, matcher: &mut ArgMatcher,
it: &mut Input, raw_args: &mut clap_lex::RawArgs,
args_cursor: clap_lex::ArgCursor,
keep_state: bool, keep_state: bool,
) -> ClapResult<()> { ) -> ClapResult<()> {
debug!("Parser::parse_subcommand"); debug!("Parser::parse_subcommand");
@ -691,7 +719,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
p.flag_subcmd_at = self.flag_subcmd_at; p.flag_subcmd_at = self.flag_subcmd_at;
p.flag_subcmd_skip = self.flag_subcmd_skip; p.flag_subcmd_skip = self.flag_subcmd_skip;
} }
if let Err(error) = p.get_matches_with(&mut sc_matcher, it) { if let Err(error) = p.get_matches_with(&mut sc_matcher, raw_args, args_cursor) {
if partial_parsing_enabled { if partial_parsing_enabled {
debug!( debug!(
"Parser::parse_subcommand: ignored error in subcommand {}: {:?}", "Parser::parse_subcommand: ignored error in subcommand {}: {:?}",
@ -804,7 +832,8 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
fn parse_long_arg( fn parse_long_arg(
&mut self, &mut self,
matcher: &mut ArgMatcher, matcher: &mut ArgMatcher,
long_arg: &RawOsStr, long_arg: Result<&str, &RawOsStr>,
long_value: Option<&RawOsStr>,
parse_state: &ParseState, parse_state: &ParseState,
valid_arg_found: &mut bool, valid_arg_found: &mut bool,
trailing_values: bool, trailing_values: bool,
@ -823,31 +852,31 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
debug!("Parser::parse_long_arg: cur_idx:={}", self.cur_idx.get()); debug!("Parser::parse_long_arg: cur_idx:={}", self.cur_idx.get());
debug!("Parser::parse_long_arg: Does it contain '='..."); debug!("Parser::parse_long_arg: Does it contain '='...");
let long_arg = match long_arg {
Ok(long_arg) => long_arg,
Err(long_arg) => {
return ParseResult::NoMatchingArg {
arg: long_arg.to_str_lossy().into_owned(),
};
}
};
if long_arg.is_empty() { if long_arg.is_empty() {
debug_assert!(long_value.is_none(), "{:?}", long_value);
return ParseResult::NoArg; return ParseResult::NoArg;
} }
let (arg, val) = if let Some(index) = long_arg.find("=") {
let (p0, p1) = long_arg.split_at(index);
debug!("Yes '{:?}'", p1);
(p0, Some(p1))
} else {
debug!("No");
(long_arg, None)
};
let opt = if let Some(opt) = self.cmd.get_keymap().get(&*arg.to_os_str()) { let opt = if let Some(opt) = self.cmd.get_keymap().get(long_arg) {
debug!( debug!(
"Parser::parse_long_arg: Found valid opt or flag '{}'", "Parser::parse_long_arg: Found valid opt or flag '{}'",
opt.to_string() opt.to_string()
); );
Some(opt) Some(opt)
} else if self.cmd.is_infer_long_args_set() { } else if self.cmd.is_infer_long_args_set() {
let arg_str = arg.to_str_lossy();
self.cmd.get_arguments().find(|a| { self.cmd.get_arguments().find(|a| {
a.long.map_or(false, |long| long.starts_with(&*arg_str)) a.long.map_or(false, |long| long.starts_with(long_arg))
|| a.aliases || a.aliases
.iter() .iter()
.any(|(alias, _)| alias.starts_with(&*arg_str)) .any(|(alias, _)| alias.starts_with(long_arg))
}) })
} else { } else {
None None
@ -859,10 +888,11 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
if opt.is_takes_value_set() { if opt.is_takes_value_set() {
debug!( debug!(
"Parser::parse_long_arg: Found an opt with value '{:?}'", "Parser::parse_long_arg: Found an opt with value '{:?}'",
&val &long_value
); );
self.parse_opt(val, opt, matcher, trailing_values) let has_eq = long_value.is_some();
} else if let Some(rest) = val { self.parse_opt(long_value, opt, matcher, trailing_values, has_eq)
} else if let Some(rest) = long_value {
let required = self.cmd.required_graph(); let required = self.cmd.required_graph();
debug!("Parser::parse_long_arg: Got invalid literal `{:?}`", rest); debug!("Parser::parse_long_arg: Got invalid literal `{:?}`", rest);
let used: Vec<Id> = matcher let used: Vec<Id> = matcher
@ -880,19 +910,21 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
used, used,
arg: opt.to_string(), arg: opt.to_string(),
} }
} else if let Some(parse_result) = self.check_for_help_and_version_str(arg) { } else if let Some(parse_result) =
self.check_for_help_and_version_str(RawOsStr::from_str(long_arg))
{
parse_result parse_result
} else { } else {
debug!("Parser::parse_long_arg: Presence validated"); debug!("Parser::parse_long_arg: Presence validated");
self.parse_flag(opt, matcher) self.parse_flag(opt, matcher)
} }
} else if let Some(sc_name) = self.possible_long_flag_subcommand(arg) { } else if let Some(sc_name) = self.possible_long_flag_subcommand(long_arg) {
ParseResult::FlagSubCommand(sc_name.to_string()) ParseResult::FlagSubCommand(sc_name.to_string())
} else if self.cmd.is_allow_hyphen_values_set() { } else if self.cmd.is_allow_hyphen_values_set() {
ParseResult::MaybeHyphenValue ParseResult::MaybeHyphenValue
} else { } else {
ParseResult::NoMatchingArg { ParseResult::NoMatchingArg {
arg: arg.to_str_lossy().into_owned(), arg: long_arg.to_owned(),
} }
} }
} }
@ -900,7 +932,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
fn parse_short_arg( fn parse_short_arg(
&mut self, &mut self,
matcher: &mut ArgMatcher, matcher: &mut ArgMatcher,
short_arg: &RawOsStr, mut short_arg: clap_lex::ShortFlags<'_>,
parse_state: &ParseState, parse_state: &ParseState,
// change this to possible pos_arg when removing the usage of &mut Parser. // change this to possible pos_arg when removing the usage of &mut Parser.
pos_counter: usize, pos_counter: usize,
@ -908,14 +940,15 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
trailing_values: bool, trailing_values: bool,
) -> ParseResult { ) -> ParseResult {
debug!("Parser::parse_short_arg: short_arg={:?}", short_arg); debug!("Parser::parse_short_arg: short_arg={:?}", short_arg);
let arg = short_arg.to_str_lossy();
#[allow(clippy::blocks_in_if_conditions)] #[allow(clippy::blocks_in_if_conditions)]
if self.cmd.is_allow_negative_numbers_set() && arg.parse::<f64>().is_ok() { if self.cmd.is_allow_negative_numbers_set() && short_arg.is_number() {
debug!("Parser::parse_short_arg: negative number"); debug!("Parser::parse_short_arg: negative number");
return ParseResult::MaybeHyphenValue; return ParseResult::MaybeHyphenValue;
} else if self.cmd.is_allow_hyphen_values_set() } else if self.cmd.is_allow_hyphen_values_set()
&& arg.chars().any(|c| !self.cmd.contains_short(c)) && short_arg
.clone()
.any(|c| !c.map(|c| self.cmd.contains_short(c)).unwrap_or_default())
{ {
debug!("Parser::parse_short_args: contains non-short flag"); debug!("Parser::parse_short_args: contains non-short flag");
return ParseResult::MaybeHyphenValue; return ParseResult::MaybeHyphenValue;
@ -943,7 +976,22 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
let skip = self.flag_subcmd_skip; let skip = self.flag_subcmd_skip;
self.flag_subcmd_skip = 0; self.flag_subcmd_skip = 0;
for c in arg.chars().skip(skip) { let res = short_arg.advance_by(skip);
debug_assert_eq!(
res,
Ok(()),
"tracking of `flag_subcmd_skip` is off for `{:?}`",
short_arg
);
while let Some(c) = short_arg.next_flag() {
let c = match c {
Ok(c) => c,
Err(rest) => {
return ParseResult::NoMatchingArg {
arg: format!("-{}", rest.to_str_lossy()),
};
}
};
debug!("Parser::parse_short_arg:iter:{}", c); debug!("Parser::parse_short_arg:iter:{}", c);
// update each index because `-abcd` is four indices to clap // update each index because `-abcd` is four indices to clap
@ -974,7 +1022,9 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
} }
// Check for trailing concatenated value // Check for trailing concatenated value
let val = short_arg.split_once(c).expect(INTERNAL_ERROR_MSG).1; //
// Cloning the iterator, so we rollback if it isn't there.
let val = short_arg.clone().next_value_os().unwrap_or_default();
debug!( debug!(
"Parser::parse_short_arg:iter:{}: val={:?} (bytes), val={:?} (ascii), short_arg={:?}", "Parser::parse_short_arg:iter:{}: val={:?} (bytes), val={:?} (ascii), short_arg={:?}",
c, val, val.as_raw_bytes(), short_arg c, val, val.as_raw_bytes(), short_arg
@ -988,7 +1038,12 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// //
// e.g. `-xvf`, when require_equals && x.min_vals == 0, we don't // e.g. `-xvf`, when require_equals && x.min_vals == 0, we don't
// consume the `vf`, even if it's provided as value. // consume the `vf`, even if it's provided as value.
match self.parse_opt(val, opt, matcher, trailing_values) { let (val, has_eq) = if let Some(val) = val.and_then(|v| v.strip_prefix('=')) {
(Some(val), true)
} else {
(val, false)
};
match self.parse_opt(val, opt, matcher, trailing_values, has_eq) {
ParseResult::AttachedValueNotConsumed => continue, ParseResult::AttachedValueNotConsumed => continue,
x => return x, x => return x,
} }
@ -997,17 +1052,12 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
return if let Some(sc_name) = self.cmd.find_short_subcmd(c) { return if let Some(sc_name) = self.cmd.find_short_subcmd(c) {
debug!("Parser::parse_short_arg:iter:{}: subcommand={}", c, sc_name); debug!("Parser::parse_short_arg:iter:{}: subcommand={}", c, sc_name);
let name = sc_name.to_string(); let name = sc_name.to_string();
let done_short_args = { // Get the index of the previously saved flag subcommand in the group of flags (if exists).
let cur_idx = self.cur_idx.get(); // If it is a new flag subcommand, then the formentioned index should be the current one
// Get the index of the previously saved flag subcommand in the group of flags (if exists). // (ie. `cur_idx`), and should be registered.
// If it is a new flag subcommand, then the formentioned index should be the current one let cur_idx = self.cur_idx.get();
// (ie. `cur_idx`), and should be registered. self.flag_subcmd_at.get_or_insert(cur_idx);
let at = *self.flag_subcmd_at.get_or_insert(cur_idx); let done_short_args = short_arg.is_empty();
// If we are done, then the difference of indices (cur_idx - at) should be (end - at) which
// should equal to (arg.len() - 1),
// where `end` is the index of the end of the group.
cur_idx - at == arg.len() - 1
};
if done_short_args { if done_short_args {
self.flag_subcmd_at = None; self.flag_subcmd_at = None;
} }
@ -1027,14 +1077,13 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
opt: &Arg<'help>, opt: &Arg<'help>,
matcher: &mut ArgMatcher, matcher: &mut ArgMatcher,
trailing_values: bool, trailing_values: bool,
has_eq: bool,
) -> ParseResult { ) -> ParseResult {
debug!( debug!(
"Parser::parse_opt; opt={}, val={:?}", "Parser::parse_opt; opt={}, val={:?}, has_eq={:?}",
opt.name, attached_value opt.name, attached_value, has_eq
); );
debug!("Parser::parse_opt; opt.settings={:?}", opt.settings); debug!("Parser::parse_opt; opt.settings={:?}", opt.settings);
// has_eq: --flag=value
let has_eq = matches!(attached_value, Some(fv) if fv.starts_with("="));
debug!("Parser::parse_opt; Checking for val..."); debug!("Parser::parse_opt; Checking for val...");
// require_equals is set, but no '=' is provided, try throwing error. // require_equals is set, but no '=' is provided, try throwing error.
@ -1064,14 +1113,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
arg: opt.to_string(), arg: opt.to_string(),
} }
} }
} else if let Some(fv) = attached_value { } else if let Some(v) = attached_value {
let v = fv.strip_prefix("=").unwrap_or(fv);
debug!("Found - {:?}, len: {}", v, v.raw_len());
debug!(
"Parser::parse_opt: {:?} contains '='...{:?}",
fv,
fv.starts_with("=")
);
self.inc_occurrence_of_arg(matcher, opt); self.inc_occurrence_of_arg(matcher, opt);
self.add_val_to_arg( self.add_val_to_arg(
opt, opt,
@ -1531,49 +1573,6 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
} }
} }
#[derive(Debug)]
pub(crate) struct Input {
items: Vec<OsString>,
cursor: usize,
}
impl<I, T> From<I> for Input
where
I: Iterator<Item = T>,
T: Into<OsString> + Clone,
{
fn from(val: I) -> Self {
Self {
items: val.map(|x| x.into()).collect(),
cursor: 0,
}
}
}
impl Input {
pub(crate) fn next(&mut self) -> Option<(&OsStr, &[OsString])> {
if self.cursor >= self.items.len() {
None
} else {
let current = &self.items[self.cursor];
self.cursor += 1;
let remaining = &self.items[self.cursor..];
Some((current, remaining))
}
}
/// Insert some items to the Input items just after current parsing cursor.
/// Usually used by replaced items recovering.
pub(crate) fn insert(&mut self, insert_items: &[&str]) {
self.items = insert_items
.iter()
.map(OsString::from)
.chain(self.items.drain(self.cursor..))
.collect();
self.cursor = 0;
}
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum ParseState { pub(crate) enum ParseState {
ValuesDone, ValuesDone,