Initial version of `argparse!`

This commit is contained in:
Daniel Grunwald 2015-08-02 21:56:03 +02:00
parent 74cdb841c6
commit 072f4d24eb
2 changed files with 171 additions and 0 deletions

View File

@ -12,6 +12,10 @@ Copyright (c) 2015 Daniel Grunwald.
Rust-cpython is licensed under the [MIT license](http://opensource.org/licenses/MIT).
Python is licensed under the [Python License](https://docs.python.org/2/license.html).
Supported Python versions:
* Python 2.7
* Python 3.3
* Python 3.4
# Usage

167
src/argparse.rs Normal file
View File

@ -0,0 +1,167 @@
// 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.
use std::ptr;
use python::{Python, PythonObject};
use objects::{PyObject, PyTuple, PyDict, PyString, exc};
use conversion::ToPyObject;
use ffi;
use err::{self, PyResult};
pub struct ParamDescription<'a> {
name: &'a str,
is_optional: bool
}
/// Parse argument list
///
/// 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`.
pub fn parse_args<'p>(
fname: Option<&str>, params: &[ParamDescription],
args: &PyTuple<'p>, kwargs: Option<&PyDict<'p>>,
output: &mut[Option<PyObject<'p>>]
) -> PyResult<'p, ()>
{
assert!(params.len() == output.len());
let py = args.python();
let nargs = args.len();
let nkeywords = kwargs.map_or(0, |d| d.len());
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() {
match kwargs.and_then(|d| d.get_item(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(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 {
// check for extraneous keyword arguments
for (key, _value) in kwargs.unwrap().items() {
let key = try!(PyString::extract(&key));
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_rules! argparse_extract {
( ( ) $body:block ) => { $body };
( ( $pname:ident : $ptype:ty ) $body:block) => {
match <$ptype as $crate::ExtractPyObject>::prepare_extract($pname.as_ref().unwrap()) {
Ok(prepared) => {
match <$ptype as $crate::ExtractPyObject>::extract(&prepared) {
Ok($pname) => $body,
Err(e) => Err(e)
}
},
Err(e) => Err(e)
}
};
( ( $pname:ident : $ptype:ty , $($r:tt)+ ) $body:block) => {
argparse_extract!(($pname: $ptype) {
argparse_extract!( ( $($r)* ) $body)
})
}
}
macro_rules! argparse_snd {
( $fst:expr, $snd:expr) => { $snd }
}
macro_rules! argparse {
($fname:expr, $args:expr, $kwargs:expr, ($( $pname:ident : $ptype:ty ),*) $body:block) => {{
const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[
$(
$crate::argparse::ParamDescription {
name: stringify!($pname),
is_optional: false
}
),*
];
let mut output = [$( argparse_snd!($pname, None) ),*];
match $crate::argparse::parse_args($fname, PARAMS, $args, $kwargs, &mut output) {
Ok(()) => {
let &[$(ref $pname),*] = &output;
argparse_extract!( ( $( $pname : $ptype ),* ) $body )
},
Err(e) => Err(e)
}
}}
}
#[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);
argparse!(None, &tuple, None, (x: &str, y: i32) {
assert_eq!(x, "abc");
assert_eq!(y, 42);
called = true;
Ok(())
}).unwrap();
assert!(called);
}
}