Merge pull request #73 from tertsdiepraam/internal-module

Move helper functions to `internal` module
This commit is contained in:
Daniel Hofstetter
2023-12-14 16:51:49 +01:00
committed by GitHub
5 changed files with 176 additions and 163 deletions
+11 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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']));
}
}