use conversion::{RefFromPyObject, ToPyObject};
use err::{self, PyResult};
use ffi;
use objects::{exc, PyDict, PyObject, PyString, PyTuple};
use python::{Python, PythonObject};
use std::ptr;
pub struct ParamDescription<'a> {
pub name: &'a str,
pub is_optional: bool,
}
impl<'a> ParamDescription<'a> {
pub fn name(&self) -> &str {
if self.name.starts_with("r#") {
&self.name[2..]
} else {
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(local_inner_macros)]
macro_rules! py_argparse {
($py:expr, $fname:expr, $args:expr, $kwargs:expr, $plist:tt $body:block) => {
py_argparse_parse_plist! { py_argparse_impl { $py, $fname, $args, $kwargs, $body, } $plist }
};
}
#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! py_argparse_parse_plist {
{ $callback:ident { $($initial_arg:tt)* } ( ) } => {
$callback! { $($initial_arg)* [] }
};
{ $callback:ident $initial_args:tt ( $( $p:tt )+ ) } => {
py_argparse_parse_plist_impl! { $callback $initial_args [] ( $($p)*, ) }
};
}
#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! py_argparse_parse_plist_impl {
{ $callback:ident { $($initial_arg:tt)* } $output:tt ( ) } => {
$callback! { $($initial_arg)* $output }
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( ** $name:ident : &$t:ty , $($tail:tt)* )
} => {
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)* )
} => {
py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {**} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( ** $name:ident , $($tail:tt)* )
} => {
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)* )
} => {
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)* )
} => {
py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {*} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( * $name:ident , $($tail:tt)* )
} => {
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)* )
} => {
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)* )
} => {
py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident , $($tail:tt)* )
} => {
py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:&$crate::PyObject = [ {} {} {} ] } ]
($($tail)*)
}
};
{ $callback:ident $initial_args:tt [ $($output:tt)* ]
( $name:ident : &$t:ty = $default:expr, $($tail:tt)* )
} => {
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)* )
} => {
py_argparse_parse_plist_impl! {
$callback $initial_args
[ $($output)* { $name:$t = [ {} {$default} {} ] } ]
($($tail)*)
}
};
}
#[macro_export(local_inner_macros)]
#[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>] = &[
$(
py_argparse_param_description! { $pname : $ptype = $detail }
),*
];
let py: $crate::Python = $py;
let mut output = [$( py_replace_expr!($pname None) ),*];
match $crate::argparse::parse_args(py, $fname, PARAMS, $args, $kwargs, &mut output) {
Ok(()) => {
let mut _iter = output.iter();
py_argparse_extract!( py, _iter, $body,
[ $( { $pname : $ptype = $detail } )* ])
},
Err(e) => Err(e)
}
}};
}
#[macro_export(local_inner_macros)]
#[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 = 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 = [ {} {} $rtype:tt ] } => (
$crate::argparse::ParamDescription {
name: stringify!($pname),
is_optional: false
}
);
{ $pname:ident : $ptype:ty = [ {} {$default:expr} {$($rtype:tt)*} ] } => (
$crate::argparse::ParamDescription {
name: stringify!($pname),
is_optional: true
}
);
}
#[macro_export(local_inner_macros)]
#[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) => 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| py_argparse_extract!($py, $iter, $body, [$($tail)*])
) {
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) => 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| 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),
}
}
#[cfg(test)]
mod test {
use conversion::ToPyObject;
use objects::PyTuple;
use 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);
}
}