use std::ptr;
use crate::conversion::{RefFromPyObject, ToPyObject};
use crate::err::{self, PyResult};
use crate::ffi;
use crate::objects::{exc, PyDict, PyObject, PyString, PyTuple};
use crate::python::{Python, PythonObject};
pub struct ParamDescription<'a> {
pub name: &'a str,
pub is_optional: bool,
}
impl<'a> ParamDescription<'a> {
pub fn name(&self) -> &str {
crate::strip_raw!(self.name)
}
}
pub fn parse_args(
py: Python,
fname: Option<&str>,
params: &[ParamDescription],
args: &PyTuple,
kwargs: Option<&PyDict>,
output: &mut [Option<PyObject>],
) -> PyResult<()> {
assert!(params.len() == output.len());
let nargs = args.len(py);
let nkeywords = kwargs.map_or(0, |d| d.len(py));
if nargs + nkeywords > params.len() {
return Err(err::PyErr::new::<exc::TypeError, _>(
py,
format!(
"{}{} takes at most {} argument{} ({} given)",
fname.unwrap_or("function"),
if fname.is_some() { "()" } else { "" },
params.len(),
if params.len() != 1 { "s" } else { "" },
nargs + nkeywords
),
));
}
let mut used_keywords = 0;
for (i, (p, out)) in params.iter().zip(output).enumerate() {
match kwargs.and_then(|d| d.get_item(py, p.name())) {
Some(kwarg) => {
*out = Some(kwarg);
used_keywords += 1;
if i < nargs {
return Err(err::PyErr::new::<exc::TypeError, _>(
py,
format!(
"Argument given by name ('{}') and position ({})",
p.name(),
i + 1
),
));
}
}
None => {
if i < nargs {
*out = Some(args.get_item(py, i));
} else {
*out = None;
if !p.is_optional {
return Err(err::PyErr::new::<exc::TypeError, _>(
py,
format!(
"Required argument ('{}') (pos {}) not found",
p.name(),
i + 1
),
));
}
}
}
}
}
if used_keywords != nkeywords {
for (key, _value) in kwargs.unwrap().items(py) {
let key = key.cast_as::<PyString>(py)?.to_string(py)?;
if !params.iter().any(|p| p.name == key) {
return Err(err::PyErr::new::<exc::TypeError, _>(
py,
format!("'{}' is an invalid keyword argument for this function", key),
));
}
}
}
Ok(())
}
#[macro_export]
macro_rules! py_argparse {
($py:expr, $fname:expr, $args:expr, $kwargs:expr, $plist:tt $body:block) => {
$crate::py_argparse_parse_plist! { py_argparse_impl { $py, $fname, $args, $kwargs, $body, } $plist }
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_argparse_parse_plist {
{ $callback:ident { $($initial_arg:tt)* } ( ) } => {
$crate::$callback! { $($initial_arg)* [] }
};
{ $callback:ident $initial_args:tt ( $( $p:tt )+ ) } => {
$crate::py_argparse_parse_plist_impl! { $callback $initial_args [] ( $($p)*, ) }
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_argparse_parse_plist_impl {
{ $callback:ident { $($initial_arg:tt)* } $output:tt ( $(,)? ) } => {
$crate::$callback! { $($initial_arg)* $output }
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( ** $name:ident : &$t:ty , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$t = [ {**} {} {$t} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( ** $name:ident : $t:ty , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {**} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( ** $name:ident , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:Option<&$crate::PyDict> = [ {**} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( * $name:ident : &$t:ty , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$t = [ {*} {} {$t} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( * $name:ident : $t:ty , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {*} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( * $name:ident , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$crate::PyTuple = [ {*} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : &$t:ty , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$t = [ {} {} {$t} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : Option<&$t:ty> , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name: std::option::Option<&$t> = [ {opt} {} {$t} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : $t:ty , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$crate::PyObject = [ {} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : Option<&$t:ty> = $default:expr , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name: std::option::Option<&$t> = [ {opt} {$default} {$t} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : &$t:ty = $default:expr, $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$t = [ {} {$default} {$t} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : $t:ty = $default:expr , $($tail:tt)* )
} => {
$crate::py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {} {$default} {} ] } ]
($($tail)*)
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_argparse_impl {
($py:expr, $fname:expr, $args:expr, $kwargs:expr, $body:block,
[
{ $pargs:ident : $pargs_type:ty = [ {*} {} {} ] }
{ $pkwargs:ident : $pkwargs_type:ty = [ {**} {} {} ] }
]
) => {{
let _py: $crate::Python = $py;
let $pargs: $pargs_type = $args;
let $pkwargs: $pkwargs_type = $kwargs;
$body
}};
($py:expr, $fname:expr, $args:expr, $kwargs:expr, $body:block,
[ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]
) => {{
const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[
$(
$crate::py_argparse_param_description! { $pname : $ptype = $detail }
),*
];
let py: $crate::Python = $py;
let mut output = [$( $crate::py_replace_expr!($pname None) ),*];
match $crate::argparse::parse_args(py, $fname, PARAMS, $args, $kwargs, &mut output) {
Ok(()) => {
let mut _iter = output.iter();
let val = $crate::py_argparse_extract!( py, _iter, $body,
[ $( { $pname : $ptype = $detail } )* ]);
val
},
Err(e) => Err(e)
}
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_argparse_raw {
($py:ident, $fname:expr, $args:expr, $kwargs:expr, $plist:tt $body:block) => {{
let args: $crate::PyTuple =
$crate::PyObject::from_borrowed_ptr($py, $args).unchecked_cast_into();
let kwargs: Option<$crate::PyDict> = $crate::argparse::get_kwargs($py, $kwargs);
let ret = $crate::py_argparse_impl!($py, $fname, &args, kwargs.as_ref(), $body, $plist);
$crate::PyDrop::release_ref(args, $py);
$crate::PyDrop::release_ref(kwargs, $py);
ret
}};
}
#[inline]
#[doc(hidden)]
pub unsafe fn get_kwargs(py: Python, ptr: *mut ffi::PyObject) -> Option<PyDict> {
if ptr.is_null() {
None
} else {
Some(PyObject::from_borrowed_ptr(py, ptr).unchecked_cast_into())
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_argparse_param_description {
{ $pname:ident : $ptype:ty = [ $info:tt {} $rtype:tt ] } => (
$crate::argparse::ParamDescription {
name: stringify!($pname),
is_optional: false
}
);
{ $pname:ident : $ptype:ty = [ $info:tt {$default:expr} $rtype:tt ] } => (
$crate::argparse::ParamDescription {
name: stringify!($pname),
is_optional: true
}
);
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_argparse_extract {
( $py:expr, $iter:expr, $body:block, [] ) => { $body };
( $py:expr, $iter:expr, $body:block,
[ { $pname:ident : $ptype:ty = [ {} {} {} ] } $($tail:tt)* ]
) => {
match <$ptype as $crate::FromPyObject>::extract($py, $iter.next().unwrap().as_ref().unwrap()) {
Ok($pname) => $crate::py_argparse_extract!($py, $iter, $body, [$($tail)*]),
Err(e) => Err(e)
}
};
( $py:expr, $iter:expr, $body:block,
[ { $pname:ident : $ptype:ty = [ {} {} {$rtype:ty} ] } $($tail:tt)* ]
) => {
match <$rtype as $crate::RefFromPyObject>::with_extracted($py,
$iter.next().unwrap().as_ref().unwrap(),
|$pname: $ptype| $crate::py_argparse_extract!($py, $iter, $body, [$($tail)*])
) {
Ok(v) => v,
Err(e) => Err(e)
}
};
( $py:expr, $iter:expr, $body:block,
[ { $pname:ident : $ptype:ty = [ {opt} {} {$rtype:ty} ] } $($tail:tt)* ]
) => {{
let v = $iter.next().unwrap().as_ref().unwrap();
let mut c = |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, $body, [$($tail)*]);
let r = if v.is_none($py) {
Ok(c(None))
} else {
<$rtype as $crate::RefFromPyObject>::with_extracted($py, v, |r: &$rtype| c(Some(r)))
};
match r {
Ok(v) => v,
Err(e) => Err(e)
}
}};
( $py:expr, $iter:expr, $body:block,
[ { $pname:ident : $ptype:ty = [ {} {$default:expr} {} ] } $($tail:tt)* ]
) => {
match $iter.next().unwrap().as_ref().map(|obj| obj.extract::<_>($py)).unwrap_or(Ok($default)) {
Ok($pname) => $crate::py_argparse_extract!($py, $iter, $body, [$($tail)*]),
Err(e) => Err(e)
}
};
( $py:expr, $iter:expr, $body:block,
[ { $pname:ident : $ptype:ty = [ {} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ]
) => {
$crate::argparse::with_extracted_or_default($py,
$iter.next().unwrap().as_ref(),
|$pname: $ptype| $crate::py_argparse_extract!($py, $iter, $body, [$($tail)*]),
$default)
};
( $py:expr, $iter:expr, $body:block,
[ { $pname:ident : $ptype:ty = [ {opt} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ]
) => {
$crate::argparse::with_extracted_optional_or_default($py,
$iter.next().unwrap().as_ref(),
|$pname: $ptype| $crate::py_argparse_extract!($py, $iter, $body, [$($tail)*]),
$default)
};
}
#[doc(hidden)]
pub fn with_extracted_or_default<P: ?Sized, R, F>(
py: Python,
obj: Option<&PyObject>,
f: F,
default: &'static P,
) -> PyResult<R>
where
F: FnOnce(&P) -> PyResult<R>,
P: RefFromPyObject,
{
match obj {
Some(obj) => match P::with_extracted(py, obj, f) {
Ok(result) => result,
Err(e) => Err(e),
},
None => f(default),
}
}
#[doc(hidden)]
pub fn with_extracted_optional_or_default<P: ?Sized, R, F>(
py: Python,
obj: Option<&PyObject>,
f: F,
default: Option<&'static P>,
) -> PyResult<R>
where
F: FnOnce(Option<&P>) -> PyResult<R>,
P: RefFromPyObject,
{
match obj {
Some(obj) => {
if obj.is_none(py) {
f(None)
} else {
match P::with_extracted(py, obj, |p| f(Some(p))) {
Ok(result) => result,
Err(e) => Err(e),
}
}
}
None => f(default),
}
}
#[cfg(test)]
mod test {
use crate::conversion::ToPyObject;
use crate::objects::PyTuple;
use crate::python::{Python, PythonObject};
#[test]
pub fn test_parse() {
let gil_guard = Python::acquire_gil();
let py = gil_guard.python();
let mut called = false;
let tuple = ("abc", 42).to_py_object(py);
py_argparse!(py, None, &tuple, None, (x: &str, y: i32) {
assert_eq!(x, "abc");
assert_eq!(y, 42);
called = true;
Ok(())
})
.unwrap();
assert!(called);
}
#[test]
pub fn test_default_param_type() {
let gil_guard = Python::acquire_gil();
let py = gil_guard.python();
let mut called = false;
let tuple = ("abc",).to_py_object(py);
py_argparse!(py, None, &tuple, None, (x) {
assert_eq!(*x, tuple.get_item(py, 0));
called = true;
Ok(())
})
.unwrap();
assert!(called);
}
#[test]
pub fn test_default_value() {
let gil_guard = Python::acquire_gil();
let py = gil_guard.python();
let mut called = false;
let tuple = (0, "foo").to_py_object(py);
py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") {
assert_eq!(x, 0);
assert_eq!(y, "foo");
called = true;
Ok(())
})
.unwrap();
assert!(called);
let mut called = false;
let tuple = PyTuple::new(py, &[]);
py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") {
assert_eq!(x, 42);
assert_eq!(y, "abc");
called = true;
Ok(())
})
.unwrap();
assert!(called);
}
}