1use std::{
2 borrow::Cow,
3 cmp::Ordering,
4 collections::{btree_map::BTreeMap, HashSet},
5 ops::{Deref, DerefMut},
6 vec::IntoIter,
7};
89use super::namespaces;
10use crate::{library::Library, nameutil::crate_name, version::Version};
1112fn is_first_char_up(s: &str) -> bool {
13 s.chars().next().unwrap().is_uppercase()
14}
1516fn check_up_eq(a: &str, b: &str) -> Ordering {
17let is_a_up = is_first_char_up(a);
18let is_b_up = is_first_char_up(b);
19if is_a_up != is_b_up {
20if is_a_up {
21return Ordering::Greater;
22 }
23return Ordering::Less;
24 }
25 Ordering::Equal
26}
2728/// This function is used by the `Imports` type to generate output like `cargo
29/// fmt` would.
30///
31/// For example:
32///
33/// ```text
34/// use gdk; // lowercases come first.
35/// use Window;
36///
37/// use gdk::foo; // lowercases come first here as well.
38/// use gdk::Foo;
39/// ```
40fn compare_imports(a: &(&String, &ImportConditions), b: &(&String, &ImportConditions)) -> Ordering {
41let s = check_up_eq(a.0, b.0);
42if s != Ordering::Equal {
43return s;
44 }
45let mut a = a.0.split("::");
46let mut b = b.0.split("::");
47loop {
48match (a.next(), b.next()) {
49 (Some(a), Some(b)) => {
50let s = check_up_eq(a, b);
51if s != Ordering::Equal {
52break s;
53 }
54let s = a.partial_cmp(b).unwrap();
55if s != Ordering::Equal {
56break s;
57 }
58 }
59 (Some(_), None) => break Ordering::Greater,
60 (None, Some(_)) => break Ordering::Less,
61 (None, None) => break Ordering::Equal,
62 }
63 }
64}
6566/// Provides assistance in generating use declarations.
67///
68/// It takes into account that use declaration referring to names within the
69/// same crate will look differently. It also avoids generating spurious
70/// declarations referring to names from within the same module as the one we
71/// are generating code for.
72#[derive(Clone, Debug, Default)]
73pub struct Imports {
74/// Name of the current crate.
75crate_name: String,
76/// Names defined within current module. It doesn't need use declaration.
77defined: HashSet<String>,
78 defaults: ImportConditions,
79 map: BTreeMap<String, ImportConditions>,
80}
8182impl Imports {
83pub fn new(gir: &Library) -> Self {
84Self {
85 crate_name: make_crate_name(gir),
86 defined: HashSet::new(),
87 defaults: ImportConditions::default(),
88 map: BTreeMap::new(),
89 }
90 }
9192pub fn with_defined(gir: &Library, name: &str) -> Self {
93Self {
94 crate_name: make_crate_name(gir),
95 defined: std::iter::once(name.to_owned()).collect(),
96 defaults: ImportConditions::default(),
97 map: BTreeMap::new(),
98 }
99 }
100101#[must_use = "ImportsWithDefault must live while defaults are needed"]
102pub fn with_defaults(
103&mut self,
104 version: Option<Version>,
105 constraint: &Option<String>,
106 ) -> ImportsWithDefault<'_> {
107let constraints = if let Some(constraint) = constraint {
108vec![constraint.clone()]
109 } else {
110vec![]
111 };
112self.defaults = ImportConditions {
113 version,
114 constraints,
115 };
116117 ImportsWithDefault::new(self)
118 }
119120fn reset_defaults(&mut self) {
121self.defaults.clear();
122 }
123124/// The goals of this function is to discard unwanted imports like "crate".
125 /// It also extends the checks in case you are implementing "X". For
126 /// example, you don't want to import "X" or "crate::X" in this case.
127fn common_checks(&self, name: &str) -> bool {
128if (!name.contains("::") && name != "xlib") || self.defined.contains(name) {
129false
130} else if let Some(name) = name.strip_prefix("crate::") {
131 !self.defined.contains(name)
132 } else {
133true
134}
135 }
136137/// Declares that `name` is defined in scope
138 ///
139 /// Removes existing imports from `self.map` and marks `name` as
140 /// available to counter future import "requests".
141pub fn add_defined(&mut self, name: &str) {
142if self.defined.insert(name.to_owned()) {
143self.map.remove(name);
144 }
145 }
146147/// Declares that name should be available through its last path component.
148 ///
149 /// For example, if name is `X::Y::Z` then it will be available as `Z`.
150 /// Uses defaults.
151pub fn add(&mut self, name: &str) {
152if !self.common_checks(name) {
153return;
154 }
155if let Some(mut name) = self.strip_crate_name(name) {
156if name == "xlib" {
157 name = if self.crate_name == "gdk_x11" {
158// Dirty little hack to allow to have correct import for GDKX11.
159Cow::Borrowed("x11::xlib")
160 } else {
161// gtk has a module named "xlib" which is why this hack is needed too.
162Cow::Borrowed("crate::xlib")
163 };
164 }
165let defaults = &self.defaults;
166let entry = self
167.map
168 .entry(name.into_owned())
169 .or_insert_with(|| defaults.clone());
170 entry.update_version(self.defaults.version);
171 entry.update_constraints(&self.defaults.constraints);
172 }
173 }
174175/// Declares that name should be available through its last path component.
176 ///
177 /// For example, if name is `X::Y::Z` then it will be available as `Z`.
178pub fn add_with_version(&mut self, name: &str, version: Option<Version>) {
179if !self.common_checks(name) {
180return;
181 }
182if let Some(name) = self.strip_crate_name(name) {
183let entry = self
184.map
185 .entry(name.into_owned())
186 .or_insert(ImportConditions {
187 version,
188 constraints: Vec::new(),
189 });
190 entry.update_version(version);
191// Since there is no constraint on this import, if any constraint
192 // is present, we can just remove it.
193entry.constraints.clear();
194 }
195 }
196197/// Declares that name should be available through its last path component
198 /// and provides an optional feature constraint.
199 ///
200 /// For example, if name is `X::Y::Z` then it will be available as `Z`.
201pub fn add_with_constraint(
202&mut self,
203 name: &str,
204 version: Option<Version>,
205 constraint: Option<&str>,
206 ) {
207if !self.common_checks(name) {
208return;
209 }
210if let Some(name) = self.strip_crate_name(name) {
211let entry = if let Some(constraint) = constraint {
212let constraint = String::from(constraint);
213let entry = self
214.map
215 .entry(name.into_owned())
216 .or_insert(ImportConditions {
217 version,
218 constraints: vec![constraint.clone()],
219 });
220 entry.add_constraint(constraint);
221 entry
222 } else {
223let entry = self
224.map
225 .entry(name.into_owned())
226 .or_insert(ImportConditions {
227 version,
228 constraints: Vec::new(),
229 });
230// Since there is no constraint on this import, if any constraint
231 // is present, we can just remove it.
232entry.constraints.clear();
233 entry
234 };
235 entry.update_version(version);
236 }
237 }
238239/// Declares that name should be available through its full path.
240 ///
241 /// For example, if name is `X::Y` then it will be available as `X::Y`.
242pub fn add_used_type(&mut self, used_type: &str) {
243if let Some(i) = used_type.find("::") {
244if i == 0 {
245self.add(&used_type[2..]);
246 } else {
247self.add(&used_type[..i]);
248 }
249 } else {
250self.add(&format!("crate::{used_type}"));
251 }
252 }
253254pub fn add_used_types(&mut self, used_types: &[String]) {
255for s in used_types {
256self.add_used_type(s);
257 }
258 }
259260/// Declares that name should be available through its full path.
261 ///
262 /// For example, if name is `X::Y` then it will be available as `X::Y`.
263pub fn add_used_type_with_version(&mut self, used_type: &str, version: Option<Version>) {
264if let Some(i) = used_type.find("::") {
265if i == 0 {
266self.add_with_version(&used_type[2..], version);
267 } else {
268self.add_with_version(&used_type[..i], version);
269 }
270 } else {
271self.add_with_version(&format!("crate::{used_type}"), version);
272 }
273 }
274275/// Tries to strip crate name prefix from given name.
276 ///
277 /// Returns `None` if name matches crate name exactly. Otherwise returns
278 /// name with crate name prefix stripped or full name if there was no match.
279fn strip_crate_name<'a>(&self, name: &'a str) -> Option<Cow<'a, str>> {
280let prefix = &self.crate_name;
281if !name.starts_with(prefix) {
282return Some(Cow::Borrowed(name));
283 }
284let rest = &name[prefix.len()..];
285if rest.is_empty() {
286None
287} else if rest.starts_with("::") {
288Some(Cow::Owned(format!("crate{rest}")))
289 } else {
290// It was false positive, return the whole name.
291Some(Cow::Borrowed(name))
292 }
293 }
294295pub fn iter(&self) -> IntoIter<(&String, &ImportConditions)> {
296let mut imports = self.map.iter().collect::<Vec<_>>();
297 imports.sort_by(compare_imports);
298 imports.into_iter()
299 }
300}
301302pub struct ImportsWithDefault<'a> {
303 imports: &'a mut Imports,
304}
305306impl<'a> ImportsWithDefault<'a> {
307fn new(imports: &'a mut Imports) -> Self {
308Self { imports }
309 }
310}
311312impl Drop for ImportsWithDefault<'_> {
313fn drop(&mut self) {
314self.imports.reset_defaults();
315 }
316}
317318impl Deref for ImportsWithDefault<'_> {
319type Target = Imports;
320fn deref(&self) -> &Self::Target {
321self.imports
322 }
323}
324325impl DerefMut for ImportsWithDefault<'_> {
326fn deref_mut(&mut self) -> &mut Self::Target {
327self.imports
328 }
329}
330331#[derive(Clone, Debug, Default, Ord, PartialEq, PartialOrd, Eq)]
332pub struct ImportConditions {
333pub version: Option<Version>,
334pub constraints: Vec<String>,
335}
336337impl ImportConditions {
338fn clear(&mut self) {
339self.version = None;
340self.constraints.clear();
341 }
342343fn update_version(&mut self, version: Option<Version>) {
344if version < self.version {
345self.version = version;
346 }
347 }
348349fn add_constraint(&mut self, constraint: String) {
350// If the import is already present but doesn't have any constraint,
351 // we don't want to add one.
352if self.constraints.is_empty() {
353return;
354 }
355// Otherwise, we just check if the constraint
356 // is already present or not before adding it.
357if !self.constraints.iter().any(|x| x == &constraint) {
358self.constraints.push(constraint);
359 }
360 }
361362fn update_constraints(&mut self, constraints: &[String]) {
363// If the import is already present but doesn't have any constraint,
364 // we don't want to add one.
365if self.constraints.is_empty() {
366return;
367 }
368if constraints.is_empty() {
369// Since there is no constraint on this import, if any constraint
370 // is present, we can just remove it.
371self.constraints.clear();
372 } else {
373// Otherwise, we just check if the constraint
374 // is already present or not before adding it.
375for constraint in constraints {
376if !self.constraints.iter().any(|x| x == constraint) {
377self.constraints.push(constraint.clone());
378 }
379 }
380 }
381 }
382}
383384fn make_crate_name(gir: &Library) -> String {
385if gir.is_glib_crate() {
386 crate_name("GLib")
387 } else {
388 crate_name(gir.namespace(namespaces::MAIN).name.as_str())
389 }
390}