2015-08-02 19:56:03 +00:00
|
|
|
// Copyright (c) 2015 Daniel Grunwald
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
//! This module contains logic for parsing a python argument list.
|
2016-03-05 16:41:04 +00:00
|
|
|
//! See also the macros `py_argparse!`, `py_fn!` and `py_method!`.
|
2015-08-02 19:56:03 +00:00
|
|
|
|
|
|
|
use std::ptr;
|
|
|
|
use python::{Python, PythonObject};
|
|
|
|
use objects::{PyObject, PyTuple, PyDict, PyString, exc};
|
|
|
|
use conversion::ToPyObject;
|
|
|
|
use ffi;
|
|
|
|
use err::{self, PyResult};
|
|
|
|
|
2015-08-02 22:06:15 +00:00
|
|
|
/// Description of a python parameter; used for `parse_args()`.
|
2015-08-02 19:56:03 +00:00
|
|
|
pub struct ParamDescription<'a> {
|
2015-08-02 22:06:15 +00:00
|
|
|
/// The name of the parameter.
|
|
|
|
pub name: &'a str,
|
|
|
|
/// Whether the parameter is optional.
|
|
|
|
pub is_optional: bool
|
2015-08-02 19:56:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse argument list
|
|
|
|
///
|
2015-08-02 22:06:15 +00:00
|
|
|
/// * fname: Name of the current function
|
|
|
|
/// * params: Declared parameters of the function
|
|
|
|
/// * args: Positional arguments
|
|
|
|
/// * kwargs: Keyword arguments
|
|
|
|
/// * output: Output array that receives the arguments.
|
|
|
|
/// Must have same length as `params` and must be initialized to `None`.
|
2015-10-25 16:55:29 +00:00
|
|
|
pub fn parse_args(
|
2015-10-26 22:52:18 +00:00
|
|
|
py: Python,
|
2015-08-02 19:56:03 +00:00
|
|
|
fname: Option<&str>, params: &[ParamDescription],
|
2015-10-25 16:55:29 +00:00
|
|
|
args: &PyTuple, kwargs: Option<&PyDict>,
|
2015-10-26 22:52:18 +00:00
|
|
|
output: &mut[Option<PyObject>]
|
2015-10-25 16:55:29 +00:00
|
|
|
) -> PyResult<()>
|
2015-08-02 19:56:03 +00:00
|
|
|
{
|
|
|
|
assert!(params.len() == output.len());
|
2015-11-07 15:52:20 +00:00
|
|
|
let nargs = args.len(py);
|
2015-10-25 16:55:29 +00:00
|
|
|
let nkeywords = kwargs.map_or(0, |d| d.len(py));
|
2015-08-02 19:56:03 +00:00
|
|
|
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;
|
|
|
|
// Iterate through the parameters and assign values to output:
|
|
|
|
for (i, (p, out)) in params.iter().zip(output).enumerate() {
|
2015-10-26 22:52:18 +00:00
|
|
|
match kwargs.and_then(|d| d.get_item(py, p.name)) {
|
2015-08-02 19:56:03 +00:00
|
|
|
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 {
|
2015-10-26 22:52:18 +00:00
|
|
|
*out = Some(args.get_item(py, i));
|
2015-08-02 19:56:03 +00:00
|
|
|
} 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 {
|
|
|
|
// check for extraneous keyword arguments
|
2015-10-25 16:55:29 +00:00
|
|
|
for (key, _value) in kwargs.unwrap().items(py) {
|
2015-10-26 22:52:18 +00:00
|
|
|
let key = try!(PyString::extract(py, &key));
|
2015-08-02 19:56:03 +00:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2015-08-02 22:06:15 +00:00
|
|
|
#[doc(hidden)]
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! py_argparse_extract {
|
2015-10-26 22:52:18 +00:00
|
|
|
( $py:ident, $iter:expr, ( ) $body:block ) => { $body };
|
|
|
|
( $py:ident, $iter:expr, ( $pname:ident : $ptype:ty ) $body:block) => {
|
|
|
|
match <$ptype as $crate::ExtractPyObject>::prepare_extract($py, $iter.next().unwrap().as_ref().unwrap()) {
|
2015-08-02 19:56:03 +00:00
|
|
|
Ok(prepared) => {
|
2015-10-26 22:52:18 +00:00
|
|
|
match <$ptype as $crate::ExtractPyObject>::extract($py, &prepared) {
|
2015-08-02 19:56:03 +00:00
|
|
|
Ok($pname) => $body,
|
|
|
|
Err(e) => Err(e)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => Err(e)
|
|
|
|
}
|
|
|
|
};
|
2015-10-26 22:52:18 +00:00
|
|
|
( $py: ident, $iter:expr, ( $pname:ident : $ptype:ty , $($r:tt)+ ) $body:block) => {
|
|
|
|
py_argparse_extract!($py, $iter, ($pname: $ptype) {
|
|
|
|
py_argparse_extract!( $py, $iter, ( $($r)* ) $body)
|
2015-08-02 19:56:03 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-02 22:06:15 +00:00
|
|
|
#[doc(hidden)]
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! py_argparse_snd {
|
|
|
|
( $fst:expr, $snd:expr ) => { $snd }
|
2015-08-02 19:56:03 +00:00
|
|
|
}
|
|
|
|
|
2015-08-02 22:06:15 +00:00
|
|
|
/// This macro is used to parse a parameter list into a set of variables.
|
|
|
|
///
|
2015-10-26 22:52:18 +00:00
|
|
|
/// Syntax: `py_argparse!(py, fname, args, kwargs, (parameter-list) { body })`
|
2015-08-02 22:06:15 +00:00
|
|
|
///
|
2015-10-26 22:52:18 +00:00
|
|
|
/// * `py`: the `Python` token
|
2015-08-02 22:06:15 +00:00
|
|
|
/// * `fname`: expression of type `Option<&str>`: Name of the function used in error messages.
|
|
|
|
/// * `args`: expression of type `&PyTuple`: The position arguments
|
|
|
|
/// * `kwargs`: expression of type `Option<&PyDict>`: The named arguments
|
|
|
|
/// * `parameter-list`: a comma-separated list of Rust parameter declarations (`name: type`).
|
|
|
|
/// The types used must implement the `ExtractPyObject` trait.
|
|
|
|
/// * `body`: expression of type `PyResult<_>`.
|
|
|
|
///
|
|
|
|
/// `py_argparse!()` expands to code that extracts values from `args` and `kwargs` and assigns
|
|
|
|
/// them to the parameters. If the extraction is successful, `py_argparse!()` evaluates
|
|
|
|
/// the body expression (where the extracted parameters are available) and returns the result
|
|
|
|
/// value of the body expression.
|
|
|
|
/// If extraction fails, `py_argparse!()` returns a failed `PyResult` without evaluating `body`.
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! py_argparse {
|
2016-03-06 12:33:57 +00:00
|
|
|
// main py_argparse!() macro
|
2015-10-26 22:52:18 +00:00
|
|
|
($py:expr, $fname:expr, $args:expr, $kwargs:expr, ($( $pname:ident : $ptype:ty ),*) $body:block) => {{
|
2015-08-02 19:56:03 +00:00
|
|
|
const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[
|
|
|
|
$(
|
|
|
|
$crate::argparse::ParamDescription {
|
|
|
|
name: stringify!($pname),
|
|
|
|
is_optional: false
|
|
|
|
}
|
|
|
|
),*
|
|
|
|
];
|
2015-10-25 16:55:29 +00:00
|
|
|
let py: $crate::Python = $py;
|
2015-08-02 22:06:15 +00:00
|
|
|
let mut output = [$( py_argparse_snd!($pname, None) ),*];
|
2015-10-26 22:52:18 +00:00
|
|
|
match $crate::argparse::parse_args(py, $fname, PARAMS, $args, $kwargs, &mut output) {
|
2015-08-02 19:56:03 +00:00
|
|
|
Ok(()) => {
|
2015-08-02 22:06:15 +00:00
|
|
|
// We can't use experimental slice pattern syntax in macros
|
|
|
|
//let &[$(ref $pname),*] = &output;
|
|
|
|
let mut iter = output.iter();
|
2015-10-26 22:52:18 +00:00
|
|
|
let ret = py_argparse_extract!( py, iter, ( $( $pname : $ptype ),* ) $body );
|
2015-08-02 22:06:15 +00:00
|
|
|
assert!(iter.next() == None);
|
|
|
|
ret
|
2015-08-02 19:56:03 +00:00
|
|
|
},
|
|
|
|
Err(e) => Err(e)
|
|
|
|
}
|
2016-03-06 12:33:57 +00:00
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
#[doc(hidden)]
|
|
|
|
macro_rules! py_argparse_declare_item_in_impl {
|
|
|
|
// argparse_declare_item_in_impl!({implhead} {head} (params) (,plist,) {tail} )
|
|
|
|
// = implhead { head(params, pname:ptype...) tail }
|
|
|
|
|
|
|
|
{ {$($implhead:tt)*} {$($head:tt)*} $params:tt ( $(,)* ) {$($tail:tt)*} } => {
|
|
|
|
py_coerce_item!{
|
|
|
|
$($implhead)* {
|
|
|
|
$($head)* $params $($tail)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
{ $implhead:tt $head:tt ($($params:tt)*) ( ,$pname:ident : $ptype:ty, $($r:tt)* ) $tail:tt } => {
|
|
|
|
py_argparse_declare_item_in_impl!( $implhead $head ($($params)* , $pname : $ptype) ( ,$($r)* ) $tail)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
#[doc(hidden)]
|
|
|
|
macro_rules! py_argparse_call_with_names {
|
|
|
|
// py_argparse_call_with_names!(f, (lhs) (,plist,))
|
|
|
|
// = f(lhs, pnames...)
|
|
|
|
|
|
|
|
( $f:expr, $lhs:tt ( $(,)* )) => (
|
|
|
|
py_coerce_expr!{ $f $lhs }
|
|
|
|
);
|
|
|
|
( $f:expr, ($($lhs:tt)*) ( ,$pname:ident : $ptype:ty, $($r:tt)* )) => {
|
|
|
|
py_argparse_call_with_names!( $f, ($($lhs)* , $pname) ( ,$($r)* ))
|
|
|
|
};
|
2015-08-02 19:56:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use python::{Python, PythonObject};
|
|
|
|
use conversion::ToPyObject;
|
|
|
|
|
|
|
|
#[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);
|
2015-10-26 22:52:18 +00:00
|
|
|
py_argparse!(py, None, &tuple, None, (x: &str, y: i32) {
|
2015-08-02 19:56:03 +00:00
|
|
|
assert_eq!(x, "abc");
|
|
|
|
assert_eq!(y, 42);
|
|
|
|
called = true;
|
|
|
|
Ok(())
|
|
|
|
}).unwrap();
|
|
|
|
assert!(called);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|