1use std::ptr;
23
24use crate::conversion::{RefFromPyObject, ToPyObject};
25use crate::err::{self, PyResult};
26use crate::ffi;
27use crate::objects::{exc, PyDict, PyObject, PyString, PyTuple};
28use crate::python::{Python, PythonObject};
29
30pub struct ParamDescription<'a> {
32 pub name: &'a str,
34 pub is_optional: bool,
36}
37
38impl<'a> ParamDescription<'a> {
39 pub fn name(&self) -> &str {
41 crate::strip_raw!(self.name)
42 }
43}
44
45pub fn parse_args(
54 py: Python,
55 fname: Option<&str>,
56 params: &[ParamDescription],
57 args: &PyTuple,
58 kwargs: Option<&PyDict>,
59 output: &mut [Option<PyObject>],
60) -> PyResult<()> {
61 assert!(params.len() == output.len());
62 let nargs = args.len(py);
63 let nkeywords = kwargs.map_or(0, |d| d.len(py));
64 if nargs + nkeywords > params.len() {
65 return Err(err::PyErr::new::<exc::TypeError, _>(
66 py,
67 format!(
68 "{}{} takes at most {} argument{} ({} given)",
69 fname.unwrap_or("function"),
70 if fname.is_some() { "()" } else { "" },
71 params.len(),
72 if params.len() != 1 { "s" } else { "" },
73 nargs + nkeywords
74 ),
75 ));
76 }
77 let mut used_keywords = 0;
78 for (i, (p, out)) in params.iter().zip(output).enumerate() {
80 match kwargs.and_then(|d| d.get_item(py, p.name())) {
81 Some(kwarg) => {
82 *out = Some(kwarg);
83 used_keywords += 1;
84 if i < nargs {
85 return Err(err::PyErr::new::<exc::TypeError, _>(
86 py,
87 format!(
88 "Argument given by name ('{}') and position ({})",
89 p.name(),
90 i + 1
91 ),
92 ));
93 }
94 }
95 None => {
96 if i < nargs {
97 *out = Some(args.get_item(py, i));
98 } else {
99 *out = None;
100 if !p.is_optional {
101 return Err(err::PyErr::new::<exc::TypeError, _>(
102 py,
103 format!(
104 "Required argument ('{}') (pos {}) not found",
105 p.name(),
106 i + 1
107 ),
108 ));
109 }
110 }
111 }
112 }
113 }
114 if used_keywords != nkeywords {
115 for (key, _value) in kwargs.unwrap().items(py) {
117 let key = key.cast_as::<PyString>(py)?.to_string(py)?;
118 if !params.iter().any(|p| p.name == key) {
119 return Err(err::PyErr::new::<exc::TypeError, _>(
120 py,
121 format!("'{}' is an invalid keyword argument for this function", key),
122 ));
123 }
124 }
125 }
126 Ok(())
127}
128
129#[macro_export]
168macro_rules! py_argparse {
169 ($py:expr, $fname:expr, $args:expr, $kwargs:expr, $plist:tt {$($body:tt)*}) => {
170 $crate::py_argparse_parse_plist! { py_argparse_impl { $py, $fname, $args, $kwargs, {$($body)*}, } $plist }
171 };
172}
173
174#[macro_export]
175#[doc(hidden)]
176macro_rules! py_argparse_parse_plist {
177 { $callback:ident { $($initial_arg:tt)* } ( ) } => {
184 $crate::$callback! { $($initial_arg)* [] }
185 };
186 { $callback:ident $initial_args:tt ( $( $p:tt )+ ) } => {
188 $crate::py_argparse_parse_plist_impl! { $callback $initial_args [] ( $($p)*, ) }
191 };
192}
193
194#[macro_export]
195#[doc(hidden)]
196macro_rules! py_argparse_parse_plist_impl {
197 { $callback:ident { $($initial_arg:tt)* } $output:tt ( $(,)? ) } => {
201 $crate::$callback! { $($initial_arg)* $output }
202 };
203 { $callback:ident $initial_args:tt [ $($output:tt)* ]
205 ( ** $name:ident : &$t:ty , $($tail:tt)* )
206 } => {
207 $crate::py_argparse_parse_plist_impl! {
208 $callback $initial_args
209 [ $($output)* { $name:&$t = [ {**} {} {$t} ] } ]
210 ($($tail)*)
211 }
212 };
213 { $callback:ident $initial_args:tt [ $($output:tt)* ]
215 ( ** $name:ident : $t:ty , $($tail:tt)* )
216 } => {
217 $crate::py_argparse_parse_plist_impl! {
218 $callback $initial_args
219 [ $($output)* { $name:$t = [ {**} {} {} ] } ]
220 ($($tail)*)
221 }
222 };
223 { $callback:ident $initial_args:tt [ $($output:tt)* ]
225 ( ** $name:ident , $($tail:tt)* )
226 } => {
227 $crate::py_argparse_parse_plist_impl! {
228 $callback $initial_args
229 [ $($output)* { $name:Option<&$crate::PyDict> = [ {**} {} {} ] } ]
230 ($($tail)*)
231 }
232 };
233 { $callback:ident $initial_args:tt [ $($output:tt)* ]
235 ( * $name:ident : &$t:ty , $($tail:tt)* )
236 } => {
237 $crate::py_argparse_parse_plist_impl! {
238 $callback $initial_args
239 [ $($output)* { $name:&$t = [ {*} {} {$t} ] } ]
240 ($($tail)*)
241 }
242 };
243 { $callback:ident $initial_args:tt [ $($output:tt)* ]
245 ( * $name:ident : $t:ty , $($tail:tt)* )
246 } => {
247 $crate::py_argparse_parse_plist_impl! {
248 $callback $initial_args
249 [ $($output)* { $name:$t = [ {*} {} {} ] } ]
250 ($($tail)*)
251 }
252 };
253 { $callback:ident $initial_args:tt [ $($output:tt)* ]
255 ( * $name:ident , $($tail:tt)* )
256 } => {
257 $crate::py_argparse_parse_plist_impl! {
258 $callback $initial_args
259 [ $($output)* { $name:&$crate::PyTuple = [ {*} {} {} ] } ]
260 ($($tail)*)
261 }
262 };
263 { $callback:ident $initial_args:tt [ $($output:tt)* ]
265 ( $name:ident : &$t:ty , $($tail:tt)* )
266 } => {
267 $crate::py_argparse_parse_plist_impl! {
268 $callback $initial_args
269 [ $($output)* { $name:&$t = [ {} {} {$t} ] } ]
270 ($($tail)*)
271 }
272 };
273 { $callback:ident $initial_args:tt [ $($output:tt)* ]
275 ( $name:ident : Option<&$t:ty> , $($tail:tt)* )
276 } => {
277 $crate::py_argparse_parse_plist_impl! {
278 $callback $initial_args
279 [ $($output)* { $name: std::option::Option<&$t> = [ {opt} {} {$t} ] } ]
280 ($($tail)*)
281 }
282 };
283 { $callback:ident $initial_args:tt [ $($output:tt)* ]
285 ( $name:ident : $t:ty , $($tail:tt)* )
286 } => {
287 $crate::py_argparse_parse_plist_impl! {
288 $callback $initial_args
289 [ $($output)* { $name:$t = [ {} {} {} ] } ]
290 ($($tail)*)
291 }
292 };
293 { $callback:ident $initial_args:tt [ $($output:tt)* ]
295 ( $name:ident , $($tail:tt)* )
296 } => {
297 $crate::py_argparse_parse_plist_impl! {
298 $callback $initial_args
299 [ $($output)* { $name:&$crate::PyObject = [ {} {} {} ] } ]
300 ($($tail)*)
301 }
302 };
303 { $callback:ident $initial_args:tt [ $($output:tt)* ]
305 ( $name:ident : Option<&$t:ty> = $default:expr , $($tail:tt)* )
306 } => {
307 $crate::py_argparse_parse_plist_impl! {
308 $callback $initial_args
309 [ $($output)* { $name: std::option::Option<&$t> = [ {opt} {$default} {$t} ] } ]
310 ($($tail)*)
311 }
312 };
313 { $callback:ident $initial_args:tt [ $($output:tt)* ]
315 ( $name:ident : &$t:ty = $default:expr, $($tail:tt)* )
316 } => {
317 $crate::py_argparse_parse_plist_impl! {
318 $callback $initial_args
319 [ $($output)* { $name:&$t = [ {} {$default} {$t} ] } ]
320 ($($tail)*)
321 }
322 };
323 { $callback:ident $initial_args:tt [ $($output:tt)* ]
325 ( $name:ident : $t:ty = $default:expr , $($tail:tt)* )
326 } => {
327 $crate::py_argparse_parse_plist_impl! {
328 $callback $initial_args
329 [ $($output)* { $name:$t = [ {} {$default} {} ] } ]
330 ($($tail)*)
331 }
332 };
333}
334
335#[macro_export]
338#[doc(hidden)]
339macro_rules! py_argparse_impl {
340 ($py:expr, $fname:expr, $args:expr, $kwargs:expr, {$($body:tt)*},
343 [
344 { $pargs:ident : $pargs_type:ty = [ {*} {} {} ] }
345 { $pkwargs:ident : $pkwargs_type:ty = [ {**} {} {} ] }
346 ]
347 ) => {{
348 let _py: $crate::Python = $py;
349 let $pargs: $pargs_type = $args;
351 let $pkwargs: $pkwargs_type = $kwargs;
352 $($body)*
353 }};
354
355 ($py:expr, $fname:expr, $args:expr, $kwargs:expr, {$($body:tt)*},
357 [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]
358 ) => {{
359 const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[
360 $(
361 $crate::py_argparse_param_description! { $pname : $ptype = $detail }
362 ),*
363 ];
364 let py: $crate::Python = $py;
365 let mut output = [$( $crate::py_replace_expr!($pname None) ),*];
366 match $crate::argparse::parse_args(py, $fname, PARAMS, $args, $kwargs, &mut output) {
367 Ok(()) => {
368 let mut _iter = output.iter();
372 let val = $crate::py_argparse_extract!( py, _iter, {$($body)*},
376 [ $( { $pname : $ptype = $detail } )* ]);
377 val
378 },
379 Err(e) => Err(e)
380 }
381 }};
382}
383
384#[macro_export]
386#[doc(hidden)]
387macro_rules! py_argparse_raw {
388 ($py:ident, $fname:expr, $args:expr, $kwargs:expr, $plist:tt {$($body:tt)*}) => {{
389 let args: $crate::PyTuple =
390 $crate::PyObject::from_borrowed_ptr($py, $args).unchecked_cast_into();
391 let kwargs: Option<$crate::PyDict> = $crate::argparse::get_kwargs($py, $kwargs);
392 let ret = $crate::py_argparse_impl!($py, $fname, &args, kwargs.as_ref(), {$($body)*}, $plist);
393 $crate::PyDrop::release_ref(args, $py);
394 $crate::PyDrop::release_ref(kwargs, $py);
395 ret
396 }};
397}
398
399#[inline]
400#[doc(hidden)]
401pub unsafe fn get_kwargs(py: Python, ptr: *mut ffi::PyObject) -> Option<PyDict> {
402 if ptr.is_null() {
403 None
404 } else {
405 Some(PyObject::from_borrowed_ptr(py, ptr).unchecked_cast_into())
406 }
407}
408
409#[macro_export]
410#[doc(hidden)]
411macro_rules! py_argparse_param_description {
412 { $pname:ident : $ptype:ty = [ $info:tt {} $rtype:tt ] } => (
414 $crate::argparse::ParamDescription {
415 name: stringify!($pname),
416 is_optional: false
417 }
418 );
419 { $pname:ident : $ptype:ty = [ $info:tt {$default:expr} $rtype:tt ] } => (
421 $crate::argparse::ParamDescription {
422 name: stringify!($pname),
423 is_optional: true
424 }
425 );
426}
427
428#[macro_export]
429#[doc(hidden)]
430macro_rules! py_argparse_extract {
431 ( $py:expr, $iter:expr, {$($body:tt)*}, [] ) => {{ $($body)* }};
433 ( $py:expr, $iter:expr, {$($body:tt)*},
435 [ { $pname:ident : $ptype:ty = [ {} {} {} ] } $($tail:tt)* ]
436 ) => {
437 match <$ptype as $crate::FromPyObject>::extract($py, $iter.next().unwrap().as_ref().unwrap()) {
440 Ok($pname) => $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
441 Err(e) => Err(e)
442 }
443 };
444 ( $py:expr, $iter:expr, {$($body:tt)*},
446 [ { $pname:ident : $ptype:ty = [ {} {} {$rtype:ty} ] } $($tail:tt)* ]
447 ) => {
448 match <$rtype as $crate::RefFromPyObject>::with_extracted($py,
451 $iter.next().unwrap().as_ref().unwrap(),
452 |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*])
453 ) {
454 Ok(v) => v,
455 Err(e) => Err(e)
456 }
457 };
458 ( $py:expr, $iter:expr, {$($body:tt)*},
460 [ { $pname:ident : $ptype:ty = [ {opt} {} {$rtype:ty} ] } $($tail:tt)* ]
461 ) => {{
462 let v = $iter.next().unwrap().as_ref().unwrap();
465 let mut c = |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]);
466 let r = if v.is_none($py) {
467 Ok(c(None))
468 } else {
469 <$rtype as $crate::RefFromPyObject>::with_extracted($py, v, |r: &$rtype| c(Some(r)))
470 };
471 match r {
472 Ok(v) => v,
473 Err(e) => Err(e)
474 }
475 }};
476 ( $py:expr, $iter:expr, {$($body:tt)*},
478 [ { $pname:ident : $ptype:ty = [ {} {$default:expr} {} ] } $($tail:tt)* ]
479 ) => {
480 match $iter.next().unwrap().as_ref().map(|obj| obj.extract::<_>($py)).unwrap_or(Ok($default)) {
481 Ok($pname) => $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
482 Err(e) => Err(e)
483 }
484 };
485 ( $py:expr, $iter:expr, {$($body:tt)*},
487 [ { $pname:ident : $ptype:ty = [ {} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ]
488 ) => {
489 $crate::argparse::with_extracted_or_default($py,
491 $iter.next().unwrap().as_ref(),
492 |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
493 $default)
494 };
495 ( $py:expr, $iter:expr, {$($body:tt)*},
497 [ { $pname:ident : $ptype:ty = [ {opt} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ]
498 ) => {
499 $crate::argparse::with_extracted_optional_or_default($py,
501 $iter.next().unwrap().as_ref(),
502 |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
503 $default)
504 };
505}
506
507#[doc(hidden)] pub fn with_extracted_or_default<P: ?Sized, R, F>(
509 py: Python,
510 obj: Option<&PyObject>,
511 f: F,
512 default: &'static P,
513) -> PyResult<R>
514where
515 F: FnOnce(&P) -> PyResult<R>,
516 P: RefFromPyObject,
517{
518 match obj {
519 Some(obj) => match P::with_extracted(py, obj, f) {
520 Ok(result) => result,
521 Err(e) => Err(e),
522 },
523 None => f(default),
524 }
525}
526
527#[doc(hidden)] pub fn with_extracted_optional_or_default<P: ?Sized, R, F>(
529 py: Python,
530 obj: Option<&PyObject>,
531 f: F,
532 default: Option<&'static P>,
533) -> PyResult<R>
534where
535 F: FnOnce(Option<&P>) -> PyResult<R>,
536 P: RefFromPyObject,
537{
538 match obj {
539 Some(obj) => {
540 if obj.is_none(py) {
541 f(None)
542 } else {
543 match P::with_extracted(py, obj, |p| f(Some(p))) {
544 Ok(result) => result,
545 Err(e) => Err(e),
546 }
547 }
548 }
549 None => f(default),
550 }
551}
552
553#[cfg(test)]
554mod test {
555 use crate::conversion::ToPyObject;
556 use crate::objects::PyTuple;
557 use crate::python::{Python, PythonObject};
558
559 #[test]
560 pub fn test_parse() {
561 let gil_guard = Python::acquire_gil();
562 let py = gil_guard.python();
563 let mut called = false;
564 let tuple = ("abc", 42).to_py_object(py);
565 py_argparse!(py, None, &tuple, None, (x: &str, y: i32) {
566 assert_eq!(x, "abc");
567 assert_eq!(y, 42);
568 called = true;
569 Ok(())
570 })
571 .unwrap();
572 assert!(called);
573 }
574
575 #[test]
576 pub fn test_default_param_type() {
577 let gil_guard = Python::acquire_gil();
578 let py = gil_guard.python();
579 let mut called = false;
580 let tuple = ("abc",).to_py_object(py);
581 py_argparse!(py, None, &tuple, None, (x) {
582 assert_eq!(*x, tuple.get_item(py, 0));
583 called = true;
584 Ok(())
585 })
586 .unwrap();
587 assert!(called);
588 }
589
590 #[test]
591 pub fn test_default_value() {
592 let gil_guard = Python::acquire_gil();
593 let py = gil_guard.python();
594 let mut called = false;
595 let tuple = (0, "foo").to_py_object(py);
596 py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") {
597 assert_eq!(x, 0);
598 assert_eq!(y, "foo");
599 called = true;
600 Ok(())
601 })
602 .unwrap();
603 assert!(called);
604
605 let mut called = false;
606 let tuple = PyTuple::new(py, &[]);
607 py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") {
608 assert_eq!(x, 42);
609 assert_eq!(y, "abc");
610 called = true;
611 Ok(())
612 })
613 .unwrap();
614 assert!(called);
615 }
616}