Skip to main content

opencv_binding_generator/
debug.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::fmt::Display;
4use std::path::{Path, PathBuf};
5use std::sync::OnceLock;
6
7use clang::Entity;
8use dunce::canonicalize;
9
10static DEBUG_CONFIG: OnceLock<Option<DebugConfig>> = OnceLock::new();
11
12#[derive(Debug)]
13pub struct DebugConfig {
14	pub installation_root: PathBuf,
15}
16
17/// Enabled debug output like symbol location in the generator output
18pub fn enable(debug_config: Option<DebugConfig>) {
19	DEBUG_CONFIG.set(debug_config).expect("Debug config is already initialized");
20}
21
22#[inline(always)]
23pub fn enabled() -> bool {
24	DEBUG_CONFIG.get().is_some()
25}
26
27#[inline(always)]
28pub fn config() -> Option<&'static DebugConfig> {
29	DEBUG_CONFIG.get().and_then(|c| c.as_ref())
30}
31
32#[derive(Clone, Debug)]
33pub struct LocationName<'me> {
34	pub location: DefinitionLocation,
35	pub name: Cow<'me, str>,
36}
37
38impl<'me> LocationName<'me> {
39	pub fn new(location: DefinitionLocation, name: impl Into<Cow<'me, str>>) -> Self {
40		Self {
41			location,
42			name: name.into(),
43		}
44	}
45}
46
47#[derive(Clone, Debug)]
48pub enum DefinitionLocation {
49	Generated,
50	File(PathBuf, u32),
51}
52
53impl DefinitionLocation {
54	pub fn as_file(&self) -> Option<(&Path, u32)> {
55		match self {
56			DefinitionLocation::Generated => None,
57			DefinitionLocation::File(path, line) => Some((path, *line)),
58		}
59	}
60}
61
62impl Display for DefinitionLocation {
63	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64		match self {
65			Self::Generated => f.write_str("generated"),
66			Self::File(file, line) => {
67				let file = canonicalize(file).expect("Can't canonicalize path");
68				let path = config()
69					.and_then(|dbg_config| file.strip_prefix(&dbg_config.installation_root).ok())
70					.unwrap_or(&file);
71				write!(f, "{}:{line}", path.display())
72			}
73		}
74	}
75}
76
77pub trait NameDebug<'me> {
78	fn file_line_name(self) -> LocationName<'me>;
79
80	fn get_debug(self) -> String
81	where
82		Self: Sized,
83	{
84		if enabled() {
85			let LocationName { location, name } = self.file_line_name();
86			format!("// {name} {location}")
87		} else {
88			"".to_string()
89		}
90	}
91}
92
93impl NameDebug<'_> for &Entity<'_> {
94	fn file_line_name(self) -> LocationName<'static> {
95		let loc = self.get_location().expect("Can't get entity location").get_file_location();
96		let mut name = self
97			.get_display_name()
98			.unwrap_or_else(|| "<unknown display name>".to_string());
99		// special handling for unnamed enums inside classes that are reported like:
100		// (unnamed enum at /path/to/file:line)
101		if let Some(unnamed) = name.strip_prefix("(")
102			&& unnamed.starts_with("unnamed ")
103		{
104			if let Some(parent_name) = self.get_semantic_parent().and_then(|p| p.get_display_name()) {
105				// resolve parent name if possible
106				name = parent_name;
107			} else if let Some((unnamed, _)) = unnamed.split_once(" at ") {
108				// just drop the location otherwise because we already write out the location separately
109				name = unnamed.to_string();
110			}
111		}
112		LocationName::new(
113			DefinitionLocation::File(loc.file.map(|f| f.get_path()).expect("Can't get file for debug"), loc.line),
114			name,
115		)
116	}
117}