opencv_binding_generator/
element.rs

1use std::borrow::Cow;
2use std::ffi::OsStr;
3use std::fmt;
4use std::path::{Component, Path};
5
6use clang::{Accessibility, Entity, EntityKind};
7
8use crate::type_ref::CppNameStyle;
9use crate::{settings, IteratorExt, StrExt, StringExt, SupportedModule};
10
11pub const UNNAMED: &str = "unnamed";
12
13pub struct DefaultElement;
14
15impl DefaultElement {
16	pub fn exclude_kind(this: &(impl Element + ?Sized)) -> ExcludeKind {
17		let cpp_refname = this.cpp_name(CppNameStyle::Reference);
18		ExcludeKind::Included.with_is_ignored(|| {
19			!this.is_public()
20				|| settings::ELEMENT_EXCLUDE_KIND
21					.get(cpp_refname.as_ref())
22					.is_some_and(|ek| ek.is_ignored())
23		})
24	}
25
26	pub fn is_system(entity: Entity) -> bool {
27		entity.is_in_system_header()
28			&& !is_opencv_path(
29				&entity
30					.get_location()
31					.expect("Can't get entity location")
32					.get_spelling_location()
33					.file
34					.expect("Can't get location path")
35					.get_path(),
36			)
37	}
38
39	pub fn is_public(entity: Entity) -> bool {
40		// MSRV: use `is_none_or` when MSRV is 1.82
41		entity.get_accessibility().map_or(true, |a| Accessibility::Public == a)
42	}
43
44	pub fn cpp_namespace(entity: Entity) -> String {
45		let mut entity = entity;
46		let mut parts = vec![];
47		while let Some(parent) = entity.get_semantic_parent() {
48			match parent.get_kind() {
49				EntityKind::ClassDecl
50				| EntityKind::Namespace
51				| EntityKind::StructDecl
52				| EntityKind::EnumDecl
53				| EntityKind::UnionDecl
54				| EntityKind::ClassTemplate
55				| EntityKind::ClassTemplatePartialSpecialization
56				| EntityKind::FunctionTemplate
57				| EntityKind::Method
58				| EntityKind::FunctionDecl => {
59					// handle anonymous enums inside classes and anonymous namespaces
60					if let Some(parent_name) = parent.get_name() {
61						parts.push(parent_name);
62					}
63				}
64				EntityKind::TranslationUnit
65				| EntityKind::UnexposedDecl
66				| EntityKind::LinkageSpec
67				| EntityKind::NotImplemented
68				| EntityKind::Constructor => {}
69				_ => {
70					unreachable!("Can't get kind of parent for cpp namespace: {:#?}", parent)
71				}
72			}
73			entity = parent;
74		}
75		parts.into_iter().rev().join("::")
76	}
77
78	pub fn cpp_decl_name_with_namespace<'t>(this: &'t (impl Element + ?Sized), decl_name: &str) -> Cow<'t, str> {
79		let mut out = this.cpp_namespace();
80		out.to_mut().extend_sep("::", decl_name);
81		out
82	}
83
84	pub fn cpp_name<'t>(this: &'t (impl Element + ?Sized), entity: Entity, style: CppNameStyle) -> Cow<'t, str> {
85		let decl_name = entity
86			.get_name()
87			.or_else(|| {
88				if matches!(
89					entity.get_kind(),
90					EntityKind::StructDecl | EntityKind::ClassDecl | EntityKind::EnumDecl
91				) {
92					// for <clang-16 the classes and enums defined with `typedef struct` will have no name
93					// and the only way to get the name from the typedef is to rely on the type's `get_display_name`
94					entity.get_type().map(|typ| {
95						typ.get_display_name()
96							.cpp_name_from_fullname(CppNameStyle::Declaration)
97							.to_string()
98					})
99				} else {
100					None
101				}
102			})
103			.map_or(Cow::Borrowed(UNNAMED), Cow::Owned);
104		match style {
105			CppNameStyle::Declaration => decl_name,
106			CppNameStyle::Reference => DefaultElement::cpp_decl_name_with_namespace(this, &decl_name),
107		}
108	}
109}
110
111pub trait Element: fmt::Debug {
112	fn update_debug_struct<'dref, 'a, 'b>(
113		&self,
114		struct_debug: &'dref mut fmt::DebugStruct<'a, 'b>,
115	) -> &'dref mut fmt::DebugStruct<'a, 'b> {
116		struct_debug
117			.field("cpp_fullname", &self.cpp_name(CppNameStyle::Reference))
118			.field("exclude_kind", &self.exclude_kind())
119			.field("is_system", &self.is_system())
120			.field("is_public", &self.is_public())
121	}
122
123	fn exclude_kind(&self) -> ExcludeKind {
124		DefaultElement::exclude_kind(self)
125	}
126
127	fn is_system(&self) -> bool;
128
129	fn is_public(&self) -> bool;
130
131	fn doc_comment(&self) -> Cow<str>;
132
133	fn cpp_namespace(&self) -> Cow<str>;
134
135	fn cpp_name(&self, style: CppNameStyle) -> Cow<str>;
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139pub enum ExcludeKind {
140	Included,
141	/// The bindings will not be generated for this element
142	Excluded,
143	/// Like [ExcludeKind::Excluded], but any element that references this element will also be excluded
144	Ignored,
145}
146
147impl ExcludeKind {
148	pub fn is_excluded(self) -> bool {
149		match self {
150			Self::Excluded | Self::Ignored => true,
151			Self::Included => false,
152		}
153	}
154
155	pub fn is_ignored(self) -> bool {
156		match self {
157			Self::Ignored => true,
158			Self::Included | Self::Excluded => false,
159		}
160	}
161
162	pub fn is_included(self) -> bool {
163		match self {
164			Self::Included => true,
165			Self::Ignored | Self::Excluded => false,
166		}
167	}
168
169	/// Return [ExcludeKind::Excluded] if `is_excluded` is true and `self` is [ExcludeKind::Included],
170	/// `self` otherwise
171	pub fn with_is_excluded(self, is_excluded: impl FnOnce() -> bool) -> ExcludeKind {
172		match self {
173			Self::Included => {
174				if is_excluded() {
175					Self::Excluded
176				} else {
177					self
178				}
179			}
180			Self::Excluded | Self::Ignored => self,
181		}
182	}
183
184	/// Return [ExcludeKind::Ignored] if `is_ignored` is true, `self` otherwise
185	pub fn with_is_ignored(self, is_ignored: impl FnOnce() -> bool) -> ExcludeKind {
186		match self {
187			Self::Included | Self::Excluded => {
188				if is_ignored() {
189					Self::Ignored
190				} else {
191					self
192				}
193			}
194			Self::Ignored => self,
195		}
196	}
197
198	/// Return the most ignored kind between `self` and `new()`
199	pub fn with_exclude_kind(self, new: impl FnOnce() -> ExcludeKind) -> ExcludeKind {
200		match self {
201			Self::Included => new(),
202			Self::Excluded => match new() {
203				Self::Ignored => Self::Ignored,
204				Self::Included | Self::Excluded => self,
205			},
206			Self::Ignored => self,
207		}
208	}
209
210	/// Like [ExcludeKind::with_exclude_kind], but will return [ExcludeKind::Excluded] if `new()`
211	/// returns [ExcludeKind::Ignored], useful for container types like [crate::Vector]
212	pub fn with_reference_exclude_kind(self, new: impl FnOnce() -> ExcludeKind) -> ExcludeKind {
213		match self {
214			Self::Included => match new() {
215				Self::Ignored => Self::Excluded,
216				Self::Included | Self::Excluded => self,
217			},
218			Self::Excluded | Self::Ignored => self,
219		}
220	}
221}
222
223pub trait EntityElement<'tu> {
224	fn entity(&self) -> Entity<'tu>;
225}
226
227pub fn is_opencv_path(path: &Path) -> bool {
228	path
229		.components()
230		.rfind(|c| {
231			if let Component::Normal(c) = c {
232				if *c == "opencv2" || *c == "Headers" {
233					return true;
234				}
235			}
236			false
237		})
238		.is_some()
239}
240
241/// Returns path component that corresponds to OpenCV module name. It's either a directory
242/// (e.g. "calib3d") or a header file (e.g. "dnn.hpp")
243fn opencv_module_component(path: &Path) -> Option<&OsStr> {
244	let mut module_comp = path
245		.components()
246		.rev()
247		.filter_map(|c| {
248			if let Component::Normal(c) = c {
249				Some(c)
250			} else {
251				None
252			}
253		})
254		.peekable();
255	let mut module = None;
256	while let Some(cur) = module_comp.next() {
257		if let Some(&parent) = module_comp.peek() {
258			if parent == "opencv2" || parent == "src_cpp" || parent == "Headers" {
259				module = Some(cur);
260				break;
261			}
262		}
263	}
264	module
265}
266
267/// Return OpenCV module from the given path
268pub fn opencv_module_from_path(path: &Path) -> Option<SupportedModule> {
269	opencv_module_component(path)
270		.and_then(|m| m.to_str())
271		.and_then(|m| m.strip_suffix(".hpp").or(Some(m)))
272		.and_then(SupportedModule::try_from_opencv_name)
273}