xcodeproj/pbxproj/object/
collection.rs

1// use md5::Digest;
2// use rand::distributions::Alphanumeric;
3// use rand::{thread_rng, Rng};
4use crate::pbxproj::*;
5use anyhow::Result;
6use std::collections::HashMap;
7
8/// [`PBXObject`] storage with convenient helper methods
9#[derive(Default, Debug, derive_new::new, derive_deref_rs::Deref)]
10pub struct PBXObjectCollection(pub(crate) HashMap<String, PBXHashMap>);
11
12/// Get PBXObject from PBXHashMap and PBXObjectCollection
13pub trait AsPBXObject<'a> {
14    /// create a pbx object out of given value
15    fn as_pbx_object(
16        id: String,
17        value: &'a PBXHashMap,
18        objects: &'a PBXObjectCollection,
19    ) -> Result<Self>
20    where
21        Self: Sized + 'a;
22}
23
24impl PBXObjectCollection {
25    /// Get T from collection
26    pub fn get<'a, T, S>(&'a self, key: S) -> Option<T>
27    where
28        T: AsPBXObject<'a> + 'a,
29        S: AsRef<str>,
30    {
31        self.0.get(key.as_ref()).and_then(|value| {
32            AsPBXObject::as_pbx_object(key.as_ref().to_string(), value, self).ok()
33        })
34    }
35
36    /// Get T from collection
37    pub fn try_get<'a, T, S>(&'a self, key: S) -> Result<T>
38    where
39        T: AsPBXObject<'a> + 'a,
40        S: AsRef<str> + std::fmt::Debug,
41    {
42        self.get(key.as_ref())
43            .ok_or_else(|| anyhow::anyhow!("{key:?} doesn't exists!"))
44    }
45
46    /// Get PBXObject a vector of type T
47    pub fn get_vec<'a, T, I, S>(&'a self, keys: I) -> Vec<T>
48    where
49        T: AsPBXObject<'a> + 'a,
50        I: IntoIterator<Item = S> + Send,
51        S: AsRef<str>,
52    {
53        keys.into_iter()
54            .flat_map(|key| self.get(key.as_ref()))
55            .collect::<Vec<_>>()
56    }
57
58    /// Get vector by vector of T by predict
59    pub fn get_vec_by<'a, T: AsPBXObject<'a> + 'a>(
60        &'a self,
61        predict: impl Fn(&(&String, &PBXHashMap)) -> bool,
62    ) -> Vec<T> {
63        self.iter()
64            .filter(predict)
65            .flat_map(|(k, _)| self.get(k))
66            .collect::<Vec<_>>()
67    }
68
69    /// Get all PBXTarget
70    pub fn targets<'a>(&'a self) -> Vec<PBXTarget<'a>> {
71        self.get_vec_by(|(_, v)| {
72            v.get_kind("isa")
73                .map(|k| k.is_pbx_target())
74                .unwrap_or_default()
75        })
76    }
77
78    /// Get all PBXProject
79    pub fn projects<'a>(&'a self) -> Vec<PBXProject<'a>> {
80        self.get_vec_by(|(_, v)| {
81            v.get_kind("isa")
82                .map(|k| k.is_pbx_project())
83                .unwrap_or_default()
84        })
85    }
86
87    /// Get all build phases
88    pub fn build_phases<'a>(&'a self) -> Vec<PBXBuildPhase<'a>> {
89        self.get_vec_by(|(_, v)| {
90            v.get_kind("isa")
91                .map(|k| k.is_pbx_build_phase())
92                .unwrap_or_default()
93        })
94    }
95
96    /// Get all build phases
97    pub fn build_configurations<'a>(&'a self) -> Vec<XCBuildConfiguration<'a>> {
98        self.get_vec_by(|(_, v)| {
99            v.get_kind("isa")
100                .map(|k| k.is_xc_build_configuration())
101                .unwrap_or_default()
102        })
103    }
104
105    /// Get all build phases
106    pub fn build_files<'a>(&'a self) -> Vec<PBXBuildFile<'a>> {
107        self.get_vec_by(|(_, v)| {
108            v.get_kind("isa")
109                .map(|k| k.is_pbx_build_file())
110                .unwrap_or_default()
111        })
112    }
113
114    /// Get all build phases
115    pub fn build_rules<'a>(&'a self) -> Vec<PBXBuildRule<'a>> {
116        self.get_vec_by(|(_, v)| {
117            v.get_kind("isa")
118                .map(|k| k.is_pbx_build_rule())
119                .unwrap_or_default()
120        })
121    }
122
123    /// Get all source code files
124    pub fn files<'a>(&'a self) -> Vec<PBXFSReference<'a>> {
125        self.get_vec_by(|(_, v)| {
126            v.get_kind("isa")
127                .map(|k| {
128                    k.is_pbx_fsreference()
129                        && k.as_pbxfs_reference()
130                            .map(|r| r.is_file())
131                            .unwrap_or_default()
132                })
133                .unwrap_or_default()
134        })
135    }
136
137    /// Get all groups
138    pub fn groups<'a>(&'a self) -> Vec<PBXFSReference<'a>> {
139        self.get_vec_by(|(_, v)| {
140            v.get_kind("isa")
141                .map(|k| {
142                    k.is_pbx_fsreference()
143                        && k.as_pbxfs_reference()
144                            .map(|r| r.is_group())
145                            .unwrap_or_default()
146                })
147                .unwrap_or_default()
148        })
149    }
150
151    /// Get All XCSwiftPackageProductDependency Objects
152    pub fn swift_package_product_dependencies<'a>(
153        &'a self,
154    ) -> Vec<XCSwiftPackageProductDependency> {
155        self.get_vec_by(|(_, v)| {
156            v.get_kind("isa")
157                .map(|k| k.is_xc_swift_package_product_dependency())
158                .unwrap_or_default()
159        })
160    }
161
162    /// Get All XCRemoteSwiftPackageReference Objects
163    pub fn swift_package_references<'a>(&'a self) -> Vec<XCRemoteSwiftPackageReference> {
164        self.get_vec_by(|(_, v)| {
165            v.get_kind("isa")
166                .map(|k| k.is_xc_remote_swift_package_reference())
167                .unwrap_or_default()
168        })
169    }
170
171    /// Get PBXTarget
172    pub fn get_target<'a>(&'a self, key: &str) -> Option<PBXTarget> {
173        self.get(key)
174    }
175
176    /// Get PBXBuildPhase
177    pub fn get_build_phase<'a>(&'a self, key: &str) -> Option<PBXBuildPhase> {
178        self.get(key)
179    }
180
181    /// Get PBXBuildFile
182    pub fn get_build_file<'a>(&'a self, key: &str) -> Option<PBXBuildFile> {
183        self.get(key)
184    }
185
186    /// Get PBXBuildRule
187    pub fn get_build_rule<'a>(&'a self, key: &str) -> Option<PBXBuildRule> {
188        self.get(key)
189    }
190
191    /// Get PBXProject
192    pub fn get_project<'a>(&'a self, key: &str) -> Option<PBXProject> {
193        self.get(key)
194    }
195
196    /// Get all files
197    pub fn get_file<'a>(&'a self, key: &str) -> Option<PBXFSReference> {
198        let fs_ref = self.get::<PBXFSReference, _>(key)?;
199        if fs_ref.is_file() {
200            Some(fs_ref)
201        } else {
202            None
203        }
204    }
205
206    /// Get all files
207    pub fn get_group<'a>(&'a self, key: &str) -> Option<PBXFSReference> {
208        let fs_ref = self.get::<PBXFSReference, _>(key)?;
209        if fs_ref.is_group() {
210            Some(fs_ref)
211        } else {
212            None
213        }
214    }
215
216    /// Get fs object
217    pub fn get_fs_object<'a>(&'a self, key: &str) -> Option<PBXFSReference> {
218        self.get(key)
219    }
220
221    /// Get PBXGroup with by name or path
222    pub fn get_group_by_name_or_path<'a, S: AsRef<str>>(
223        &'a self,
224        name_or_path: S,
225    ) -> Option<PBXFSReference> {
226        let name = name_or_path.as_ref();
227        self.groups().into_iter().find(|o| {
228            if let Some(n) = o.name {
229                return n == name;
230            } else if let Some(p) = o.path {
231                return p == name;
232            } else {
233                false
234            }
235        })
236    }
237
238    /// Get build configurations shearing a given baseConfiguration id
239    pub fn get_build_configurations_by_base_id<S: AsRef<str>>(
240        &self,
241        id: S,
242    ) -> Vec<XCBuildConfiguration> {
243        let key = id.as_ref();
244        self.get_vec_by(move |(_, v)| {
245            v.get_kind("isa")
246                .map(|o| o.is_xc_build_configuration())
247                .unwrap_or_default()
248                && v.get_string("baseConfigurationReference")
249                    .map(|s| s.as_str())
250                    == Some(key)
251        })
252    }
253
254    /// Get XCSwiftPackageProductDependency by reference
255    pub fn get_swift_package_product_dependency<'a>(
256        &'a self,
257        key: &str,
258    ) -> Option<XCSwiftPackageProductDependency> {
259        self.get(key)
260    }
261
262    /// Get XCSwiftPackageProductDependency by reference
263    pub fn get_swift_package_reference<'a>(
264        &'a self,
265        key: &str,
266    ) -> Option<XCRemoteSwiftPackageReference> {
267        self.get(key)
268    }
269
270    /// Get PBXTarget by the target name
271    pub fn get_target_by_name<'a>(&'a self, name: &'a str) -> Option<PBXTarget> {
272        self.targets().into_iter().find(|target| {
273            if let Some(target_name) = target.name {
274                target_name == name
275            } else {
276                false
277            }
278        })
279    }
280}