pyo3/impl_/pyclass/
doc.rs1use std::{ffi::CStr, marker::PhantomData};
2
3use crate::{impl_::pyclass::PyClassImpl, PyClass, PyTypeInfo};
4
5pub trait PyClassNewTextSignature {
10 const TEXT_SIGNATURE: &'static str;
11}
12
13pub struct PyClassDocGenerator<
22 ClassT: PyClass,
23 const HAS_NEW_TEXT_SIGNATURE: bool,
25>(PhantomData<ClassT>);
26
27impl<ClassT: PyClass + PyClassNewTextSignature> PyClassDocGenerator<ClassT, true> {
28 pub const DOC_PIECES: &'static [&'static [u8]] = &[
29 <ClassT as PyTypeInfo>::NAME.as_bytes(),
30 ClassT::TEXT_SIGNATURE.as_bytes(),
31 b"\n--\n\n",
32 <ClassT as PyClassImpl>::RAW_DOC.to_bytes_with_nul(),
33 ];
34}
35
36impl<ClassT: PyClass> PyClassDocGenerator<ClassT, false> {
37 pub const DOC_PIECES: &'static [&'static [u8]] =
38 &[<ClassT as PyClassImpl>::RAW_DOC.to_bytes_with_nul()];
39}
40
41pub const fn doc_bytes_as_cstr(bytes: &'static [u8]) -> &'static ::std::ffi::CStr {
43 match CStr::from_bytes_with_nul(bytes) {
44 Ok(cstr) => cstr,
45 #[cfg(not(from_bytes_with_nul_error))] Err(_) => panic!("invalid pyclass doc"),
47 #[cfg(from_bytes_with_nul_error)]
48 Err(std::ffi::FromBytesWithNulError::InteriorNul { .. }) => {
50 panic!("pyclass doc contains nul bytes")
51 }
52 #[cfg(from_bytes_with_nul_error)]
55 Err(std::ffi::FromBytesWithNulError::NotNulTerminated) => {
56 panic!("pyclass doc expected to be nul terminated")
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63
64 use crate::ffi;
65
66 use super::*;
67
68 #[test]
69 #[cfg(feature = "macros")]
70 fn test_doc_generator() {
71 use crate::impl_::concat::{combine_to_array, combined_len};
72
73 #[crate::pyclass(crate = "crate")]
75 struct MyClass;
76
77 #[crate::pymethods(crate = "crate")]
78 impl MyClass {
79 #[new]
80 fn new(x: i32, y: i32) -> Self {
81 let _ = (x, y); MyClass
83 }
84 }
85
86 const PIECES: &[&[u8]] = PyClassDocGenerator::<MyClass, true>::DOC_PIECES;
88 assert_eq!(
89 &combine_to_array::<{ combined_len(PIECES) }>(PIECES),
90 b"MyClass(x, y)\n--\n\nA dummy class with signature.\0"
91 );
92
93 const PIECES_WITHOUT_SIGNATURE: &[&[u8]] =
95 PyClassDocGenerator::<MyClass, false>::DOC_PIECES;
96 assert_eq!(
97 &combine_to_array::<{ combined_len(PIECES_WITHOUT_SIGNATURE) }>(
98 PIECES_WITHOUT_SIGNATURE
99 ),
100 b"A dummy class with signature.\0"
101 );
102 }
103
104 #[test]
105 fn test_doc_bytes_as_cstr() {
106 let cstr = doc_bytes_as_cstr(b"MyClass\0");
107 assert_eq!(cstr, ffi::c_str!("MyClass"));
108 }
109
110 #[test]
111 #[cfg(from_bytes_with_nul_error)]
112 #[should_panic(expected = "pyclass doc contains nul bytes")]
113 fn test_doc_bytes_as_cstr_central_nul() {
114 doc_bytes_as_cstr(b"MyClass\0Foo");
115 }
116
117 #[test]
118 #[cfg(from_bytes_with_nul_error)]
119 #[should_panic(expected = "pyclass doc expected to be nul terminated")]
120 fn test_doc_bytes_as_cstr_not_nul_terminated() {
121 doc_bytes_as_cstr(b"MyClass");
122 }
123}