use crate::{ffi, PyObject, Python};
use libc::c_int;
use std::ffi::CStr;
use std::fmt;
#[derive(Debug)]
pub enum PyMethodDefType {
New(PyMethodDef),
Call(PyMethodDef),
Class(PyMethodDef),
Static(PyMethodDef),
Method(PyMethodDef),
ClassAttribute(PyClassAttributeDef),
Getter(PyGetterDef),
Setter(PySetterDef),
}
#[derive(Copy, Clone, Debug)]
pub enum PyMethodType {
PyCFunction(ffi::PyCFunction),
PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords),
PyNewFunc(ffi::newfunc),
PyInitFunc(ffi::initproc),
}
#[derive(Clone, Debug)]
pub struct PyMethodDef {
pub(crate) ml_name: &'static CStr,
pub(crate) ml_meth: PyMethodType,
pub(crate) ml_flags: c_int,
pub(crate) ml_doc: &'static CStr,
}
#[derive(Copy, Clone)]
pub struct PyClassAttributeDef {
pub(crate) name: &'static CStr,
pub(crate) meth: for<'p> fn(Python<'p>) -> PyObject,
}
#[derive(Clone, Debug)]
pub struct PyGetterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: ffi::getter,
doc: &'static CStr,
}
#[derive(Clone, Debug)]
pub struct PySetterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: ffi::setter,
doc: &'static CStr,
}
unsafe impl Sync for PyMethodDef {}
unsafe impl Sync for ffi::PyMethodDef {}
unsafe impl Sync for PyGetterDef {}
unsafe impl Sync for PySetterDef {}
unsafe impl Sync for ffi::PyGetSetDef {}
fn get_name(name: &str) -> &CStr {
CStr::from_bytes_with_nul(name.as_bytes())
.expect("Method name must be terminated with NULL byte")
}
fn get_doc(doc: &str) -> &CStr {
CStr::from_bytes_with_nul(doc.as_bytes()).expect("Document must be terminated with NULL byte")
}
impl PyMethodDef {
pub(crate) fn get_new_func(&self) -> Option<ffi::newfunc> {
if let PyMethodType::PyNewFunc(new_func) = self.ml_meth {
Some(new_func)
} else {
None
}
}
pub(crate) fn get_cfunction_with_keywords(&self) -> Option<ffi::PyCFunctionWithKeywords> {
if let PyMethodType::PyCFunctionWithKeywords(func) = self.ml_meth {
Some(func)
} else {
None
}
}
pub fn cfunction(name: &'static str, cfunction: ffi::PyCFunction, doc: &'static str) -> Self {
Self {
ml_name: get_name(name),
ml_meth: PyMethodType::PyCFunction(cfunction),
ml_flags: ffi::METH_NOARGS,
ml_doc: get_doc(doc),
}
}
pub fn new_func(name: &'static str, newfunc: ffi::newfunc, doc: &'static str) -> Self {
Self {
ml_name: get_name(name),
ml_meth: PyMethodType::PyNewFunc(newfunc),
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc: get_doc(doc),
}
}
pub fn cfunction_with_keywords(
name: &'static str,
cfunction: ffi::PyCFunctionWithKeywords,
flags: c_int,
doc: &'static str,
) -> Self {
Self {
ml_name: get_name(name),
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
ml_flags: flags | ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc: get_doc(doc),
}
}
pub fn as_method_def(&self) -> ffi::PyMethodDef {
let meth = match self.ml_meth {
PyMethodType::PyCFunction(meth) => meth,
PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth) },
PyMethodType::PyNewFunc(meth) => unsafe { std::mem::transmute(meth) },
PyMethodType::PyInitFunc(meth) => unsafe { std::mem::transmute(meth) },
};
ffi::PyMethodDef {
ml_name: self.ml_name.as_ptr(),
ml_meth: Some(meth),
ml_flags: self.ml_flags,
ml_doc: self.ml_doc.as_ptr(),
}
}
}
impl PyClassAttributeDef {
pub fn new(name: &'static str, meth: for<'p> fn(Python<'p>) -> PyObject) -> Self {
Self {
name: get_name(name),
meth,
}
}
}
impl fmt::Debug for PyClassAttributeDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyClassAttributeDef")
.field("name", &self.name)
.finish()
}
}
impl PyGetterDef {
pub fn new(name: &'static str, getter: ffi::getter, doc: &'static str) -> Self {
Self {
name: get_name(name),
meth: getter,
doc: get_doc(doc),
}
}
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
if dst.name.is_null() {
dst.name = self.name.as_ptr() as _;
}
if dst.doc.is_null() {
dst.doc = self.doc.as_ptr() as _;
}
dst.get = Some(self.meth);
}
}
impl PySetterDef {
pub fn new(name: &'static str, setter: ffi::setter, doc: &'static str) -> Self {
Self {
name: get_name(name),
meth: setter,
doc: get_doc(doc),
}
}
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
if dst.name.is_null() {
dst.name = self.name.as_ptr() as _;
}
if dst.doc.is_null() {
dst.doc = self.doc.as_ptr() as _;
}
dst.set = Some(self.meth);
}
}
pub trait PyMethods {
fn py_methods() -> Vec<&'static PyMethodDefType> {
Vec::new()
}
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub trait PyMethodsInventory: inventory::Collect {
fn new(methods: Vec<PyMethodDefType>) -> Self;
fn get(&'static self) -> &'static [PyMethodDefType];
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub trait HasMethodsInventory {
type Methods: PyMethodsInventory;
}
#[cfg(feature = "macros")]
impl<T: HasMethodsInventory> PyMethods for T {
fn py_methods() -> Vec<&'static PyMethodDefType> {
inventory::iter::<T::Methods>
.into_iter()
.flat_map(PyMethodsInventory::get)
.collect()
}
}