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