diff --git a/pyo3cls/src/py_class.rs b/pyo3cls/src/py_class.rs index 84a793d2..1ee5b05c 100644 --- a/pyo3cls/src/py_class.rs +++ b/pyo3cls/src/py_class.rs @@ -6,9 +6,12 @@ use std::collections::HashMap; use syn; use quote::Tokens; +use utils; + pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens { let params = parse_attribute(attr); + let doc = utils::get_doc(&ast.attrs); let base = syn::Ident::from("_pyo3::PyObject"); let mut token: Option = None; @@ -26,7 +29,7 @@ pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens { } let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident)); - let tokens = impl_class(&ast.ident, &base, token, params); + let tokens = impl_class(&ast.ident, &base, token, doc, params); quote! { #[feature(specialization)] @@ -42,7 +45,8 @@ pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens { } fn impl_class(cls: &syn::Ident, base: &syn::Ident, - token: Option, params: HashMap<&'static str, syn::Ident>) -> Tokens { + token: Option, doc: syn::Lit, + params: HashMap<&'static str, syn::Ident>) -> Tokens { let cls_name = match params.get("name") { Some(name) => quote! { #name }.as_str().to_string(), None => quote! { #cls }.as_str().to_string() @@ -133,9 +137,12 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, ((bs + align - 1) / align * align) as isize } - #[inline] fn type_name() -> &'static str { #cls_name } + fn type_description() -> &'static str { + #doc + } + #[inline] fn type_object() -> &'static mut _pyo3::ffi::PyTypeObject { static mut TYPE_OBJECT: _pyo3::ffi::PyTypeObject = _pyo3::ffi::PyTypeObject_INIT; @@ -153,11 +160,11 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, if (ty.tp_flags & _pyo3::ffi::Py_TPFLAGS_READY) == 0 { // automatically initialize the class on-demand let to = _pyo3::typeob::initialize_type::<#cls>( - py, None, <#cls as _pyo3::typeob::PyTypeInfo>::type_name(), ty) - .expect( - format!("An error occurred while initializing class {}", - <#cls as _pyo3::typeob::PyTypeInfo>::type_name()) - .as_ref()); + py, None, <#cls as _pyo3::typeob::PyTypeInfo>::type_name(), + <#cls as _pyo3::typeob::PyTypeInfo>::type_description(), ty).expect( + format!("An error occurred while initializing class {}", + <#cls as _pyo3::typeob::PyTypeInfo>::type_name()) + .as_ref()); py.release(to); } }); diff --git a/pyo3cls/src/py_proto.rs b/pyo3cls/src/py_proto.rs index 288edc8b..d0052e1f 100644 --- a/pyo3cls/src/py_proto.rs +++ b/pyo3cls/src/py_proto.rs @@ -67,7 +67,9 @@ pub fn build_py_proto(ast: &mut syn::Item) -> Tokens { } } -fn impl_proto_impl(ty: &Box, impls: &mut Vec, proto: &defs::Proto) -> Tokens { +fn impl_proto_impl(ty: &Box, + impls: &mut Vec, proto: &defs::Proto) -> Tokens +{ let mut tokens = Tokens::new(); let mut py_methods = Vec::new(); diff --git a/pyo3cls/src/utils.rs b/pyo3cls/src/utils.rs index 816f95ed..36caaa63 100644 --- a/pyo3cls/src/utils.rs +++ b/pyo3cls/src/utils.rs @@ -1,3 +1,4 @@ +use syn; use quote::{Tokens, ToTokens}; @@ -11,3 +12,21 @@ pub fn for_err_msg(i: &ToTokens) -> String { i.to_tokens(&mut tokens); tokens.as_str().to_string() } + +pub fn get_doc(attrs: &Vec) -> syn::Lit { + let mut doc = Vec::new(); + + for attr in attrs.iter() { + match attr.value { + syn::MetaItem::NameValue(ref ident, ref lit) => { + if ident.as_ref() == "doc" { + let s = quote!{ #lit }.to_string(); + doc.push(s[1..s.len()-1].to_owned()) + } + } + _ => (), + } + } + let doc = doc.join("\n"); + syn::Lit::Str(format!("{}\0", doc), syn::StrStyle::Cooked) +} diff --git a/src/objects/module.rs b/src/objects/module.rs index 0ced1bc9..b94dc36c 100644 --- a/src/objects/module.rs +++ b/src/objects/module.rs @@ -112,7 +112,10 @@ impl<'p> PyModule { } else { // automatically initialize the class let name = self.name(py)?; - let to = ::typeob::initialize_type::(py, Some(name), type_name, ty) + let type_description = ::type_description(); + + let to = ::typeob::initialize_type::( + py, Some(name), type_name, type_description, ty) .expect(format!("An error occurred while initializing class {}", ::type_name()).as_ref()); py.release(to); diff --git a/src/typeob.rs b/src/typeob.rs index 72a41eb5..416abe86 100644 --- a/src/typeob.rs +++ b/src/typeob.rs @@ -29,6 +29,9 @@ pub trait PyTypeInfo { /// Type name fn type_name() -> &'static str; + /// Type description + fn type_description() -> &'static str { "\0" } + /// PyTypeObject instance for this type fn type_object() -> &'static mut ffi::PyTypeObject; @@ -131,7 +134,8 @@ impl PyTypeObject for T where T: PyObjectAlloc + PyTypeInfo { if (ty.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { // automatically initialize the class on-demand let to = initialize_type::( - py, None, ::type_name(), ty).expect( + py, None, ::type_name(), + ::type_description(), ty).expect( format!("An error occurred while initializing class {}", ::type_name()).as_ref()); py.release(to); @@ -148,7 +152,8 @@ impl PyTypeObject for T where T: PyObjectAlloc + PyTypeInfo { pub fn initialize_type(py: Python, module_name: Option<&str>, type_name: &str, - type_object: &mut ffi::PyTypeObject) -> PyResult + type_description: &'static str, type_object: &mut ffi::PyTypeObject) + -> PyResult where T: PyObjectAlloc + PyTypeInfo { // type name @@ -160,6 +165,7 @@ pub fn initialize_type(py: Python, module_name: Option<&str>, type_name: &str "Module name/type name must not contain NUL byte").into_raw(); type_object.tp_name = name; + type_object.tp_doc = type_description.as_ptr() as *const _; // dealloc type_object.tp_dealloc = Some(tp_dealloc_callback::); diff --git a/tests/test_class.rs b/tests/test_class.rs index a56bf6cd..de7c8dfd 100644 --- a/tests/test_class.rs +++ b/tests/test_class.rs @@ -51,6 +51,18 @@ fn empty_class() { py_assert!(py, typeobj, "typeobj.__name__ == 'EmptyClass'"); } +#[py::class] +/// Line1 +/// Line2 +struct ClassWithDocs { } + +#[test] +fn class_with_docstr() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let typeobj = py.get_type::(); + py_run!(py, typeobj, "assert typeobj.__doc__ == '/// Line1\\n/// Line2'"); +} #[py::class(name=CustomName)] struct EmptyClass2 { }