You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
Merge pull request #73 from tertsdiepraam/internal-module
Move helper functions to `internal` module
This commit is contained in:
+11
-8
@@ -254,7 +254,7 @@ pub fn long_handling(args: &[Argument], help_flags: &Flags) -> TokenStream {
|
||||
|
||||
quote!(
|
||||
let long_options: [&str; #num_opts] = [#(#options),*];
|
||||
let long = ::uutils_args::infer_long_option(long, &long_options)?;
|
||||
let long = ::uutils_args::internal::infer_long_option(long, &long_options)?;
|
||||
|
||||
#help_check
|
||||
|
||||
@@ -284,7 +284,7 @@ pub fn free_handling(args: &[Argument]) -> TokenStream {
|
||||
|
||||
if_expressions.push(quote!(
|
||||
if let Some(inner) = #filter(arg) {
|
||||
let value = ::uutils_args::parse_value_for_option("", ::std::ffi::OsStr::new(inner))?;
|
||||
let value = ::uutils_args::internal::parse_value_for_option("", ::std::ffi::OsStr::new(inner))?;
|
||||
let _ = raw.next();
|
||||
return Ok(Some(Argument::Custom(Self::#ident(value))));
|
||||
}
|
||||
@@ -308,7 +308,7 @@ pub fn free_handling(args: &[Argument]) -> TokenStream {
|
||||
dd_args.push(prefix);
|
||||
dd_branches.push(quote!(
|
||||
if prefix == #prefix {
|
||||
let value = ::uutils_args::parse_value_for_option("", ::std::ffi::OsStr::new(value))?;
|
||||
let value = ::uutils_args::internal::parse_value_for_option("", ::std::ffi::OsStr::new(value))?;
|
||||
let _ = raw.next();
|
||||
return Ok(Some(Argument::Custom(Self::#ident(value))));
|
||||
}
|
||||
@@ -321,7 +321,10 @@ pub fn free_handling(args: &[Argument]) -> TokenStream {
|
||||
if let Some((prefix, value)) = arg.split_once('=') {
|
||||
#(#dd_branches)*
|
||||
|
||||
return Err(::uutils_args::Error::UnexpectedOption(prefix.to_string(), ::uutils_args::filter_suggestions(prefix, &[#(#dd_args),*], "")));
|
||||
return Err(::uutils_args::Error::UnexpectedOption(
|
||||
prefix.to_string(),
|
||||
::uutils_args::internal::filter_suggestions(prefix, &[#(#dd_args),*], "")
|
||||
));
|
||||
}
|
||||
));
|
||||
}
|
||||
@@ -410,19 +413,19 @@ fn default_value_expression(ident: &Ident, default_expr: &TokenStream) -> TokenS
|
||||
|
||||
fn optional_value_expression(ident: &Ident, default_expr: &TokenStream) -> TokenStream {
|
||||
quote!(match parser.optional_value() {
|
||||
Some(value) => Self::#ident(::uutils_args::parse_value_for_option(&option, &value)?),
|
||||
Some(value) => Self::#ident(::uutils_args::internal::parse_value_for_option(&option, &value)?),
|
||||
None => Self::#ident(#default_expr),
|
||||
})
|
||||
}
|
||||
|
||||
fn required_value_expression(ident: &Ident) -> TokenStream {
|
||||
quote!(Self::#ident(::uutils_args::parse_value_for_option(&option, &parser.value()?)?))
|
||||
quote!(Self::#ident(::uutils_args::internal::parse_value_for_option(&option, &parser.value()?)?))
|
||||
}
|
||||
|
||||
fn positional_expression(ident: &Ident) -> TokenStream {
|
||||
// TODO: Add option name in this from_value call
|
||||
quote!(
|
||||
Self::#ident(::uutils_args::parse_value_for_option("", &value)?)
|
||||
Self::#ident(::uutils_args::internal::parse_value_for_option("", &value)?)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -432,7 +435,7 @@ fn last_positional_expression(ident: &Ident) -> TokenStream {
|
||||
let raw_args = parser.raw_args()?;
|
||||
let collection = std::iter::once(value)
|
||||
.chain(raw_args)
|
||||
.map(|v| ::uutils_args::parse_value_for_option("", &v))
|
||||
.map(|v| ::uutils_args::internal::parse_value_for_option("", &v))
|
||||
.collect::<Result<_,_>>()?;
|
||||
Self::#ident(collection)
|
||||
})
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ pub fn help_string(
|
||||
}
|
||||
|
||||
let options = if !options.is_empty() {
|
||||
quote!(::uutils_args::print_flags(&mut w, #indent, #width, [#(#options),*])?;)
|
||||
quote!(::uutils_args::internal::print_flags(&mut w, #indent, #width, [#(#options),*])?;)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ pub fn arguments(input: TokenStream) -> TokenStream {
|
||||
// This is a bit of a hack to support `echo` and should probably not be
|
||||
// used in general.
|
||||
let next_arg = if arguments_attr.parse_echo_style {
|
||||
quote!(if let Some(val) = uutils_args::__echo_style_positional(parser, &[#(#short_flags),*]) {
|
||||
quote!(if let Some(val) = ::uutils_args::internal::echo_style_positional(parser, &[#(#short_flags),*]) {
|
||||
Some(lexopt::Arg::Value(val))
|
||||
} else {
|
||||
parser.next()?
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Functions to be used by `uutils-args-derive`.
|
||||
//!
|
||||
//! This has the following implications:
|
||||
//! - These functions are not guaranteed to be stable.
|
||||
//! - These functions should not be used outside the derive crate
|
||||
//!
|
||||
//! Yet, they should be properly documented to make macro-expanded code
|
||||
//! readable.
|
||||
|
||||
use super::{Error, Value};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
/// Parses an echo-style positional argument
|
||||
///
|
||||
/// This means that any argument that does not solely consist of a hyphen
|
||||
/// followed by the characters in the list of `short_args` is considered
|
||||
/// to be a positional argument, instead of an invalid argument. This
|
||||
/// includes the `--` argument, which is ignored by `echo`.
|
||||
pub fn echo_style_positional(p: &mut lexopt::Parser, short_args: &[char]) -> Option<OsString> {
|
||||
let mut raw = p.try_raw_args()?;
|
||||
let val = raw.peek()?;
|
||||
|
||||
if is_echo_style_positional(val, short_args) {
|
||||
let val = val.into();
|
||||
raw.next();
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_echo_style_positional(s: &OsStr, short_args: &[char]) -> bool {
|
||||
let s = match s.to_str() {
|
||||
Some(x) => x,
|
||||
// If it's invalid utf-8 then it can't be a short arg, so must
|
||||
// be a positional argument.
|
||||
None => return true,
|
||||
};
|
||||
let mut chars = s.chars();
|
||||
let is_short_args = chars.next() == Some('-') && chars.all(|c| short_args.contains(&c));
|
||||
!is_short_args
|
||||
}
|
||||
|
||||
/// Parse an argument defined by a prefix
|
||||
pub fn parse_prefix<T: Value>(parser: &mut lexopt::Parser, prefix: &'static str) -> Option<T> {
|
||||
let mut raw = parser.try_raw_args()?;
|
||||
|
||||
// TODO: The to_str call is a limitation. Maybe we need to pull in something like bstr
|
||||
let arg = raw.peek()?.to_str()?;
|
||||
let value_str = arg.strip_prefix(prefix)?;
|
||||
|
||||
let value = T::from_value(OsStr::new(value_str)).ok()?;
|
||||
|
||||
// Consume the argument we just parsed
|
||||
let _ = raw.next();
|
||||
|
||||
Some(value)
|
||||
}
|
||||
|
||||
/// Parse a value and wrap the error into an `Error::ParsingFailed`
|
||||
pub fn parse_value_for_option<T: Value>(opt: &str, v: &OsStr) -> Result<T, Error> {
|
||||
T::from_value(v).map_err(|e| Error::ParsingFailed {
|
||||
option: opt.into(),
|
||||
value: v.to_string_lossy().to_string(),
|
||||
error: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// Expand unambiguous prefixes to a list of candidates
|
||||
pub fn infer_long_option<'a>(
|
||||
input: &'a str,
|
||||
long_options: &'a [&'a str],
|
||||
) -> Result<&'a str, Error> {
|
||||
let mut candidates = Vec::new();
|
||||
let mut exact_match = None;
|
||||
for opt in long_options {
|
||||
if *opt == input {
|
||||
exact_match = Some(opt);
|
||||
break;
|
||||
} else if opt.starts_with(input) {
|
||||
candidates.push(opt);
|
||||
}
|
||||
}
|
||||
|
||||
match (exact_match, &candidates[..]) {
|
||||
(Some(opt), _) => Ok(*opt),
|
||||
(None, [opt]) => Ok(**opt),
|
||||
(None, []) => Err(Error::UnexpectedOption(
|
||||
format!("--{input}"),
|
||||
filter_suggestions(input, long_options, "--"),
|
||||
)),
|
||||
(None, _) => Err(Error::AmbiguousOption {
|
||||
option: input.to_string(),
|
||||
candidates: candidates.iter().map(|s| s.to_string()).collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter a list of options to just the elements that are similar to the given string
|
||||
pub fn filter_suggestions(input: &str, long_options: &[&str], prefix: &str) -> Vec<String> {
|
||||
long_options
|
||||
.iter()
|
||||
.filter(|opt| strsim::jaro(input, opt) > 0.7)
|
||||
.map(|o| format!("{prefix}{o}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Print a formatted list of options.
|
||||
pub fn print_flags(
|
||||
mut w: impl Write,
|
||||
indent_size: usize,
|
||||
width: usize,
|
||||
options: impl IntoIterator<Item = (&'static str, &'static str)>,
|
||||
) -> std::io::Result<()> {
|
||||
let indent = " ".repeat(indent_size);
|
||||
writeln!(w, "\nOptions:")?;
|
||||
for (flags, help_string) in options {
|
||||
let mut help_lines = help_string.lines();
|
||||
write!(w, "{}{}", &indent, &flags)?;
|
||||
|
||||
if flags.len() <= width {
|
||||
let line = match help_lines.next() {
|
||||
Some(line) => line,
|
||||
None => {
|
||||
writeln!(w)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let help_indent = " ".repeat(width - flags.len() + 2);
|
||||
writeln!(w, "{}{}", help_indent, line)?;
|
||||
} else {
|
||||
writeln!(w)?;
|
||||
}
|
||||
|
||||
let help_indent = " ".repeat(width + indent_size + 2);
|
||||
for line in help_lines {
|
||||
writeln!(w, "{}{}", help_indent, line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use super::is_echo_style_positional;
|
||||
|
||||
#[test]
|
||||
fn echo_positional() {
|
||||
assert!(is_echo_style_positional(OsStr::new("-aaa"), &['b']));
|
||||
assert!(is_echo_style_positional(OsStr::new("--"), &['b']));
|
||||
assert!(!is_echo_style_positional(OsStr::new("-b"), &['b']));
|
||||
}
|
||||
}
|
||||
+2
-153
@@ -4,6 +4,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod error;
|
||||
pub mod internal;
|
||||
mod value;
|
||||
|
||||
pub use lexopt;
|
||||
@@ -12,11 +13,7 @@ pub use uutils_args_derive::*;
|
||||
pub use error::Error;
|
||||
pub use value::{Value, ValueError, ValueResult};
|
||||
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
io::Write,
|
||||
marker::PhantomData,
|
||||
};
|
||||
use std::{ffi::OsString, marker::PhantomData};
|
||||
|
||||
/// A wrapper around a type implementing [`Arguments`] that adds `Help`
|
||||
/// and `Version` variants.
|
||||
@@ -225,29 +222,6 @@ pub trait Options<Arg: Arguments>: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an echo-style positional argument
|
||||
///
|
||||
/// This means that any argument that does not solely consist of a hyphen
|
||||
/// followed by the characters in the list of `short_args` is considered
|
||||
/// to be a positional argument, instead of an invalid argument. This
|
||||
/// includes the `--` argument, which is ignored by `echo`.
|
||||
///
|
||||
/// This function is hidden and prefixed with `__` because it should only
|
||||
/// be called via the derive macros.
|
||||
#[doc(hidden)]
|
||||
pub fn __echo_style_positional(p: &mut lexopt::Parser, short_args: &[char]) -> Option<OsString> {
|
||||
let mut raw = p.try_raw_args()?;
|
||||
let val = raw.peek()?;
|
||||
|
||||
if is_echo_style_positional(val, short_args) {
|
||||
let val = val.into();
|
||||
raw.next();
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parse-is-complete")]
|
||||
fn print_complete<I, O: Options<Arg>, Arg: Arguments>(mut args: I)
|
||||
where
|
||||
@@ -263,128 +237,3 @@ where
|
||||
assert!(args.next().is_none(), "completion only takes one argument");
|
||||
println!("{}", O::complete(&shell));
|
||||
}
|
||||
|
||||
fn is_echo_style_positional(s: &OsStr, short_args: &[char]) -> bool {
|
||||
let s = match s.to_str() {
|
||||
Some(x) => x,
|
||||
// If it's invalid utf-8 then it can't be a short arg, so must
|
||||
// be a positional argument.
|
||||
None => return true,
|
||||
};
|
||||
let mut chars = s.chars();
|
||||
let is_short_args = chars.next() == Some('-') && chars.all(|c| short_args.contains(&c));
|
||||
!is_short_args
|
||||
}
|
||||
|
||||
/// Parse an argument defined by a prefix
|
||||
#[doc(hidden)]
|
||||
pub fn parse_prefix<T: Value>(parser: &mut lexopt::Parser, prefix: &'static str) -> Option<T> {
|
||||
let mut raw = parser.try_raw_args()?;
|
||||
|
||||
// TODO: The to_str call is a limitation. Maybe we need to pull in something like bstr
|
||||
let arg = raw.peek()?.to_str()?;
|
||||
let value_str = arg.strip_prefix(prefix)?;
|
||||
|
||||
let value = T::from_value(OsStr::new(value_str)).ok()?;
|
||||
|
||||
// Consume the argument we just parsed
|
||||
let _ = raw.next();
|
||||
|
||||
Some(value)
|
||||
}
|
||||
|
||||
/// Parse a value and wrap the error into an `Error::ParsingFailed`
|
||||
#[doc(hidden)]
|
||||
pub fn parse_value_for_option<T: Value>(opt: &str, v: &OsStr) -> Result<T, Error> {
|
||||
T::from_value(v).map_err(|e| Error::ParsingFailed {
|
||||
option: opt.into(),
|
||||
value: v.to_string_lossy().to_string(),
|
||||
error: e,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn infer_long_option<'a>(
|
||||
input: &'a str,
|
||||
long_options: &'a [&'a str],
|
||||
) -> Result<&'a str, Error> {
|
||||
let mut candidates = Vec::new();
|
||||
let mut exact_match = None;
|
||||
for opt in long_options {
|
||||
if *opt == input {
|
||||
exact_match = Some(opt);
|
||||
break;
|
||||
} else if opt.starts_with(input) {
|
||||
candidates.push(opt);
|
||||
}
|
||||
}
|
||||
|
||||
match (exact_match, &candidates[..]) {
|
||||
(Some(opt), _) => Ok(*opt),
|
||||
(None, [opt]) => Ok(**opt),
|
||||
(None, []) => Err(Error::UnexpectedOption(
|
||||
format!("--{input}"),
|
||||
filter_suggestions(input, long_options, "--"),
|
||||
)),
|
||||
(None, _) => Err(Error::AmbiguousOption {
|
||||
option: input.to_string(),
|
||||
candidates: candidates.iter().map(|s| s.to_string()).collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter a list of options to just the elements that are similar to the given string
|
||||
pub fn filter_suggestions(input: &str, long_options: &[&str], prefix: &str) -> Vec<String> {
|
||||
long_options
|
||||
.iter()
|
||||
.filter(|opt| strsim::jaro(input, opt) > 0.7)
|
||||
.map(|o| format!("{prefix}{o}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn print_flags(
|
||||
mut w: impl Write,
|
||||
indent_size: usize,
|
||||
width: usize,
|
||||
options: impl IntoIterator<Item = (&'static str, &'static str)>,
|
||||
) -> std::io::Result<()> {
|
||||
let indent = " ".repeat(indent_size);
|
||||
writeln!(w, "\nOptions:")?;
|
||||
for (flags, help_string) in options {
|
||||
let mut help_lines = help_string.lines();
|
||||
write!(w, "{}{}", &indent, &flags)?;
|
||||
|
||||
if flags.len() <= width {
|
||||
let line = match help_lines.next() {
|
||||
Some(line) => line,
|
||||
None => {
|
||||
writeln!(w)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let help_indent = " ".repeat(width - flags.len() + 2);
|
||||
writeln!(w, "{}{}", help_indent, line)?;
|
||||
} else {
|
||||
writeln!(w)?;
|
||||
}
|
||||
|
||||
let help_indent = " ".repeat(width + indent_size + 2);
|
||||
for line in help_lines {
|
||||
writeln!(w, "{}{}", help_indent, line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use crate::is_echo_style_positional;
|
||||
|
||||
#[test]
|
||||
fn echo_positional() {
|
||||
assert!(is_echo_style_positional(OsStr::new("-aaa"), &['b']));
|
||||
assert!(is_echo_style_positional(OsStr::new("--"), &['b']));
|
||||
assert!(!is_echo_style_positional(OsStr::new("-b"), &['b']));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user