diff --git a/Cargo.toml b/Cargo.toml index 17f2f56..d3d6872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcodeproj" -version = "0.2.3" +version = "0.2.4" edition = "2021" description = "xcodeproj reader and parser." license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index ef355a1..425e3f7 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # XcodeProj -Work-in-progress XcodeProj reader and writer. +XcodeProj reader and writer. -Currently optimized for reading and not garanted to be used to modify existing xcodeproj. +Currently optimized for reading. please see docs for usage. ## Milestones - [x] parse `*.xcodeproj` through [pest] - [x] parse [pest] ast to `PBXRootObject`, as an meaningful abstraction. -- [ ] add helper methods to maniuplate and read pbxproj objects. -- [ ] write `ProjectData` back to `*.xcodeproj` filetype. +- [ ] add helper methods to manipulate and read pbxproj objects. +- [ ] write to `*.xcodeproj` filetype. - [ ] preserve comments and reduce git conflicts. - [ ] support reading XCWorkspace and XCScheme diff --git a/src/lib.rs b/src/lib.rs index 43667ab..6f310b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,59 @@ #![deny(rustdoc::broken_intra_doc_links)] #![doc = include_str!("../README.md")] +use anyhow::Result; +use pbxproj::{PBXFSReference, PBXObjectCollection, PBXProject, PBXRootObject}; +use std::path::{Path, PathBuf}; + mod macros; pub mod pbxproj; pub mod xcode; + +/// Main presentation of XCodeProject +pub struct XCodeProject { + root: PathBuf, + pbxproj: PBXRootObject, +} + +impl XCodeProject { + /// Create new XCodeProject object + pub fn new>(xcodeproj_folder: P) -> Result { + let xcodeproj_folder = xcodeproj_folder.as_ref(); + let pbxproj_path = xcodeproj_folder.join("project.pbxproj"); + + Ok(Self { + root: xcodeproj_folder.parent().unwrap().to_path_buf(), + pbxproj: pbxproj_path.try_into()?, + }) + } + + /// Get archive version + pub fn archive_version(&self) -> u8 { + self.pbxproj.archive_version() + } + + /// Get pbxproj object version + pub fn object_version(&self) -> u8 { + self.pbxproj.object_version() + } + + /// Get root project of pbxproj + pub fn root_project(&self) -> PBXProject { + self.pbxproj.root_project() + } + + /// Get root group of pbxproj + pub fn root_group(&self) -> PBXFSReference { + self.pbxproj.root_group() + } + + /// Get pbxproj objects + pub fn objects(&self) -> &PBXObjectCollection { + self.pbxproj.objects() + } + + /// Get mutable reference of pbxproj objects + pub fn objects_mut(&mut self) -> &mut PBXObjectCollection { + self.pbxproj.objects_mut() + } +} diff --git a/src/pbxproj/mod.rs b/src/pbxproj/mod.rs index 261fd98..8adbff4 100644 --- a/src/pbxproj/mod.rs +++ b/src/pbxproj/mod.rs @@ -1,9 +1,205 @@ //! pbxproj file serialize and deserializer mod object; -mod rep; mod value; pub(crate) mod pest; pub use object::*; -pub use rep::*; pub use value::*; + +use anyhow::Result; +use std::path::{Path, PathBuf}; +use tap::Pipe; + +/// `Main` Representation of project.pbxproj file +#[derive(Debug, derive_new::new, derive_deref_rs::Deref)] +pub struct PBXRootObject { + /// archiveVersion + archive_version: u8, + /// objectVersion + object_version: u8, + /// classes + classes: PBXHashMap, + /// Objects + #[deref] + objects: PBXObjectCollection, + /// rootObjectReference + root_object_reference: String, +} + +impl PBXRootObject { + /// Get the pbxproject's archive version. + #[must_use] + pub fn archive_version(&self) -> u8 { + self.archive_version + } + + /// Get the pbxproject's object version. + #[must_use] + pub fn object_version(&self) -> u8 { + self.object_version + } + + /// Get a reference to the pbxproject's classes. + #[must_use] + pub fn classes(&self) -> &PBXHashMap { + &self.classes + } + + /// Get a reference to the pbxproject's root object reference. + #[must_use] + pub fn root_object_reference(&self) -> &str { + self.root_object_reference.as_ref() + } + + /// Get Root PBXProject + pub fn root_project(&self) -> PBXProject { + self.objects + .projects() + .into_iter() + .find(|o| o.id == self.root_object_reference()) + .unwrap() + } + + /// Get root group + pub fn root_group(&self) -> PBXFSReference { + self.root_project().main_group + } + + /// Get a reference to the pbxroot object's objects. + #[must_use] + pub fn objects(&self) -> &PBXObjectCollection { + &self.objects + } + + /// Get a mutable reference to the pbxroot object's objects. + #[must_use] + pub fn objects_mut(&mut self) -> &mut PBXObjectCollection { + &mut self.objects + } +} + +impl TryFrom for PBXRootObject { + type Error = anyhow::Error; + fn try_from(mut map: PBXHashMap) -> Result { + let archive_version = map.try_remove_number("archiveVersion")? as u8; + let object_version = map.try_remove_number("objectVersion")? as u8; + let classes = map.try_remove_object("classes").unwrap_or_default(); + let root_object_reference = map.try_remove_string("rootObject")?; + let objects = PBXObjectCollection( + map.try_remove_object("objects")? + .0 + .into_iter() + .map(|(k, v)| (k, v.try_into_object().unwrap())) + .collect(), + ); + + Ok(Self { + archive_version, + object_version, + classes, + objects, + root_object_reference, + }) + } +} + +impl TryFrom<&str> for PBXRootObject { + type Error = anyhow::Error; + fn try_from(content: &str) -> Result { + use crate::pbxproj::pest::PBXProjectParser; + + PBXProjectParser::try_from_str(content)?.pipe(Self::try_from) + } +} + +impl TryFrom for PBXRootObject { + type Error = anyhow::Error; + fn try_from(content: String) -> Result { + Self::try_from(content.as_str()) + } +} + +impl TryFrom<&Path> for PBXRootObject { + type Error = anyhow::Error; + + fn try_from(value: &Path) -> Result { + std::fs::read_to_string(&value) + .map_err(|e| anyhow::anyhow!("PBXProjectData from path {value:?}: {e}"))? + .pipe(TryFrom::try_from) + } +} + +impl TryFrom for PBXRootObject { + type Error = anyhow::Error; + + fn try_from(value: PathBuf) -> Result { + Self::try_from(value.as_path()) + } +} + +#[test] +fn test_demo1_representation() { + let test_content = include_str!("../../tests/samples/demo1.pbxproj"); + let project = PBXRootObject::try_from(test_content).unwrap(); + let targets = project.targets(); + + assert_eq!(1, targets.len()); + assert_eq!(&PBXTargetKind::Native, targets[0].kind); + assert_eq!(Some(&String::from("Wordle")), targets[0].product_name); + assert_eq!(Some(&String::from("Wordle")), targets[0].name); + assert_eq!(PBXProductType::Application, targets[0].product_type); + assert_eq!(None, targets[0].build_tool_path); + assert_eq!(None, targets[0].build_arguments_string); + assert_eq!(None, targets[0].build_working_directory); + assert_eq!(None, targets[0].pass_build_settings_in_environment); + assert_eq!(3, targets[0].build_phases.len()); + assert_eq!( + vec![ + (&PBXBuildPhaseKind::Sources, 12), // 12 + (&PBXBuildPhaseKind::Resources, 3), // 3 + (&PBXBuildPhaseKind::Frameworks, 1) // 1 + ], + targets[0] + .build_phases + .iter() + .map(|phase| (&phase.kind, phase.files.len())) + .collect::>() + ); + + assert_eq!(1, project.projects().len()); + + let root_group = project.root_group(); + assert_eq!(17, project.files().len()); + println!("{:#?}", root_group.children); + assert_eq!(3, root_group.children.len()); + assert_eq!(None, root_group.name); + assert_eq!(None, root_group.path); +} + +#[cfg(test)] +macro_rules! test_demo_file { + ($name:expr) => {{ + let (root, name) = (env!("CARGO_MANIFEST_DIR"), stringify!($name)); + let path = format!("{root}/tests/samples/{name}.pbxproj"); + let file = crate::pbxproj::PBXRootObject::try_from(std::path::PathBuf::from(path)); + if file.is_err() { + println!("Error: {:#?}", file.as_ref().unwrap_err()) + } + assert!(file.is_ok()); + file.unwrap() + }}; +} + +#[cfg(test)] +mod tests { + macro_rules! test_samples { + ($($name:ident),*) => { + $(#[test] + fn $name() { + test_demo_file!($name); + })* + }; + } + + test_samples![demo1, demo2, demo3, demo4, demo5, demo6, demo7, demo8, demo9]; +} diff --git a/src/pbxproj/object/build/config.rs b/src/pbxproj/object/build/config.rs index 216a50b..af23ede 100644 --- a/src/pbxproj/object/build/config.rs +++ b/src/pbxproj/object/build/config.rs @@ -1,51 +1,37 @@ -use crate::pbxproj::{PBXHashMap, PBXObjectCollection, PBXObjectExt, PBXRootObject}; -use std::{cell::RefCell, rc::Weak}; +use crate::pbxproj::*; /// [`PBXObject`] specifying build configurations /// /// [`PBXObject`]: crate::pbxproj::PBXObject #[derive(Debug, derive_new::new)] -pub struct XCBuildConfiguration { +pub struct XCBuildConfiguration<'a> { + /// ID Reference + pub id: String, /// The configuration name. - pub name: String, + pub name: &'a String, /// A map of build settings. - pub build_settings: PBXHashMap, - /// Base xcconfig file reference. - base_configuration_reference: Option, - objects: Weak>, + pub build_settings: &'a PBXHashMap, + /// Base xcconfig file. + pub base_configuration: Option>, } -impl XCBuildConfiguration { - /// GGet Base xcconfig file reference. - pub fn base_configuration(&self, _data: PBXRootObject) -> Option<()> { - todo!() - } - - /// Base xcconfig file reference. - pub fn set_base_configuration(&mut self, reference: Option) -> Option { - let old = self.base_configuration_reference.take(); - self.base_configuration_reference = reference; - old - } -} - -impl PBXObjectExt for XCBuildConfiguration { - fn from_hashmap( - mut value: PBXHashMap, - objects: Weak>, +impl<'a> AsPBXObject<'a> for XCBuildConfiguration<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, ) -> anyhow::Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - name: value.try_remove_string("name")?, - build_settings: value.try_remove_object("buildSettings")?, - base_configuration_reference: value.remove_string("base_configuration_reference"), - objects, + id, + name: value.try_get_string("name")?, + build_settings: value.try_get_object("buildSettings")?, + base_configuration: value + .get_value("baseConfigurationReference") + .and_then(|v| v.as_string()) + .and_then(|key| objects.get(key)), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/build/list.rs b/src/pbxproj/object/build/list.rs index ed54588..51f479e 100644 --- a/src/pbxproj/object/build/list.rs +++ b/src/pbxproj/object/build/list.rs @@ -4,96 +4,94 @@ use crate::pbxproj::*; /// /// [`PBXObject`]: crate::pbxproj::PBXObject #[derive(Debug, derive_new::new)] -pub struct XCConfigurationList { +pub struct XCConfigurationList<'a> { + /// ID Reference + pub id: String, /// Element build configurations. - build_configuration_references: Vec, + pub build_configurations: Vec>, /// Element default configuration is visible. pub default_configuration_is_visible: bool, /// Element default configuration name - pub default_configuration_name: Option, - objects: WeakPBXObjectCollection, + pub default_configuration_name: Option<&'a String>, } - -impl XCConfigurationList { - /// Build configurations - pub fn set_build_configuration_references(&mut self, references: Vec) -> Vec { - let old = std::mem::replace(&mut self.build_configuration_references, references); - old +impl<'a> AsPBXObject<'a> for XCConfigurationList<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> anyhow::Result + where + Self: Sized + 'a, + { + Ok(Self { + id, + build_configurations: value + .get_vec("buildConfigurations") + .map(|v| objects.get_vec(v.as_vec_strings())) + .unwrap_or_default(), + default_configuration_is_visible: value + .try_get_number("defaultConfigurationIsVisible")? + == &1, + default_configuration_name: value.get_string("defaultConfigurationName"), + }) } +} - /// Build configurations - // pub fn get_build_configurations<'a>( - // &'a self, - // data: &'a PBXRootObject, - // ) -> Vec<&'a XCBuildConfiguration> { - // self.build_configuration_references - // .iter() - // .map(|r| Some(data.get(r)?.borrow().as_xc_build_configuration()?)) - // .flatten() - // .collect() - // } - - /// Returns the build configuration with the given name (if it exists) - // pub fn get_configuration_by_name<'a>( - // &'a self, - // data: &'a PBXRootObject, - // name: &'a str, - // ) -> Option<&'a XCBuildConfiguration> { - // self.get_build_configurations(data) - // .into_iter() - // .find(|o| &o.name == name) - // } +// impl XCConfigurationList { +// /// Build configurations +// pub fn set_build_configuration_references(&mut self, references: Vec) -> Vec { +// let old = std::mem::replace(&mut self.build_configuration_references, references); +// old +// } - /// Adds the default configurations, debug and release - // pub fn add_default_configurations(&mut self, data: &mut PBXRootObject) { - // let mut configurations = vec![]; - // let debug = XCBuildConfiguration::new("Debug".into(), Default::default(), None); - // let debug_id = data.push(debug); +// /// Build configurations +// // pub fn get_build_configurations<'a>( +// // &'a self, +// // data: &'a PBXRootObject, +// // ) -> Vec<&'a XCBuildConfiguration> { +// // self.build_configuration_references +// // .iter() +// // .map(|r| Some(data.get(r)?.borrow().as_xc_build_configuration()?)) +// // .flatten() +// // .collect() +// // } - // configurations.push(debug_id); +// /// Returns the build configuration with the given name (if it exists) +// // pub fn get_configuration_by_name<'a>( +// // &'a self, +// // data: &'a PBXRootObject, +// // name: &'a str, +// // ) -> Option<&'a XCBuildConfiguration> { +// // self.get_build_configurations(data) +// // .into_iter() +// // .find(|o| &o.name == name) +// // } - // let release = XCBuildConfiguration::new("Release".into(), Default::default(), None); - // let release_id = data.push(release); +// /// Adds the default configurations, debug and release +// // pub fn add_default_configurations(&mut self, data: &mut PBXRootObject) { +// // let mut configurations = vec![]; +// // let debug = XCBuildConfiguration::new("Debug".into(), Default::default(), None); +// // let debug_id = data.push(debug); - // configurations.push(release_id); +// // configurations.push(debug_id); - // self.build_configuration_references.extend(configurations); - // } +// // let release = XCBuildConfiguration::new("Release".into(), Default::default(), None); +// // let release_id = data.push(release); - /// Returns the object with the given configuration list (project or target) - pub fn object_with_configuration_list(&self, _data: &PBXRootObject) -> Option<&PBXObject> { - // projects, Native target, aggregateTargets, legacyTargets build_configuration_list_reference +// // configurations.push(release_id); - // data.iter().find(|o| { - // match o { - // PBXObject::PBXProject(p) => p - // } - // }); - todo!() - } -} +// // self.build_configuration_references.extend(configurations); +// // } -impl PBXObjectExt for XCConfigurationList { - fn from_hashmap( - mut value: PBXHashMap, - objects: crate::pbxproj::WeakPBXObjectCollection, - ) -> anyhow::Result - where - Self: Sized, - { - Ok(Self { - build_configuration_references: value - .try_remove_vec("buildConfigurations")? - .try_into_vec_strings()?, - default_configuration_is_visible: value - .try_remove_number("defaultConfigurationIsVisible")? - == 1, - default_configuration_name: value.remove_string("defaultConfigurationName"), - objects, - }) - } +// /// Returns the object with the given configuration list (project or target) +// pub fn object_with_configuration_list(&self, _data: &PBXRootObject) -> Option<&PBXObject> { +// // projects, Native target, aggregateTargets, legacyTargets build_configuration_list_reference - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } -} +// // data.iter().find(|o| { +// // match o { +// // PBXObject::PBXProject(p) => p +// // } +// // }); +// todo!() +// } +// } diff --git a/src/pbxproj/object/build/phase/file.rs b/src/pbxproj/object/build/phase/file.rs index dda9a62..829926c 100644 --- a/src/pbxproj/object/build/phase/file.rs +++ b/src/pbxproj/object/build/phase/file.rs @@ -1,98 +1,41 @@ -use std::{cell::RefCell, rc::Rc}; - use crate::pbxproj::*; /// [`PBXObject`] A File referenced by a build phase, unique to each build phase. -#[derive(Default, Debug, derive_new::new)] -pub struct PBXBuildFile { +#[derive(Default, Debug, derive_new::new, derive_deref_rs::Deref)] +pub struct PBXBuildFile<'a> { + /// ID Reference + pub id: String, /// Element settings - pub settings: Option, + pub settings: Option<&'a PBXValue>, /// Platform filter attribute. - pub platform_filter: Option, + pub platform_filter: Option<&'a String>, /// Element file reference. - file_reference: Option, + #[deref] + pub file: Option>, /// Product reference. - product_reference: Option, + pub product: Option>, /// The cached build phase this build file belongs to - build_phase_reference: Option, - objects: WeakPBXObjectCollection, -} - -impl PBXBuildFile { - /// Returns the file the build file refers to. - pub fn get_file(&self, _data: &PBXRootObject) -> Option<&PBXObject> { - // fileReference?.getObject() - todo!() - } - - /// Create new [`PBXBuildFile`] from product_reference only - pub fn new_from_swift_product(reference: String, objects: WeakPBXObjectCollection) -> Self { - Self { - settings: Default::default(), - platform_filter: Default::default(), - file_reference: Default::default(), - product_reference: Some(reference), - build_phase_reference: Default::default(), - objects, - } - } - - /// Returns the file the build file refers to. - pub fn set_file_reference(&mut self, reference: Option) -> Option { - std::mem::replace(&mut self.file_reference, reference) - } - - /// Get Product using self.product_reference - pub fn product(&self) -> Option>> { - self.objects - .upgrade()? - .borrow() - .get_swift_package_product_dependency(self.product_reference.as_ref()?) - } - - /// Set Product. - pub fn set_product_reference(&mut self, reference: Option) -> Option { - std::mem::replace(&mut self.file_reference, reference) - } - - /// Set the pbxbuild file's build phase reference. - pub fn set_build_phase_reference(&mut self, reference: Option) -> Option { - std::mem::replace(&mut self.build_phase_reference, reference) - } - - /// Get a reference to the pbxbuild file's build phase reference. - #[must_use] - pub fn build_phase_reference(&self) -> Option<&String> { - self.build_phase_reference.as_ref() - } - - /// Get filename - fn filename(&self, _data: &PBXRootObject) -> Option { - todo!() - } - - /// Returns the type of the build phase the build file belongs to. - fn build_phase(&self, _data: &PBXRootObject) -> Option<&PBXObject> { - todo!() - } + pub build_phase: Option>, } -impl PBXObjectExt for PBXBuildFile { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result +impl<'a> AsPBXObject<'a> for PBXBuildFile<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> anyhow::Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - settings: value.remove_value("settings"), - platform_filter: value.remove_string("platformFilter"), - file_reference: value.remove_string("fileRef"), - product_reference: value.remove_string("productRef"), - build_phase_reference: value.remove_string("buildPhaseReference"), - objects, + id, + settings: value.get_value("settings"), + platform_filter: value.get_string("platformFilter"), + file: value.get_string("fileRef").and_then(|k| objects.get(k)), + product: value.get_string("productRef").and_then(|k| objects.get(k)), + build_phase: value + .get_string("buildPhaseReference") + .and_then(|k| objects.get(k)), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/build/phase/kind.rs b/src/pbxproj/object/build/phase/kind.rs index a5e43a4..e733c0f 100644 --- a/src/pbxproj/object/build/phase/kind.rs +++ b/src/pbxproj/object/build/phase/kind.rs @@ -2,7 +2,7 @@ use derive_is_enum_variant::is_enum_variant; use tap::Pipe; /// Enum that encapsulates all kind of build phases available in Xcode. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, is_enum_variant)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, is_enum_variant)] pub enum PBXBuildPhaseKind { /// Sources Sources, diff --git a/src/pbxproj/object/build/phase/mod.rs b/src/pbxproj/object/build/phase/mod.rs index 81b3685..093f224 100644 --- a/src/pbxproj/object/build/phase/mod.rs +++ b/src/pbxproj/object/build/phase/mod.rs @@ -9,109 +9,30 @@ pub use rule::*; pub use script::*; use crate::pbxproj::*; -use std::collections::HashSet; /// `Abstraction` of build phase variants. #[derive(Debug, derive_new::new)] -pub struct PBXBuildPhase { +pub struct PBXBuildPhase<'a> { + /// ID Reference + pub id: String, /// Element build action mask. pub build_action_mask: isize, /// References to build files. - pub file_references: Option>, + pub files: Vec>, /// Paths to the input file lists. - pub input_file_list_paths: Option>, + pub input_file_list_paths: Option>, /// Paths to the output file lists. - pub output_file_list_paths: Option>, + pub output_file_list_paths: Option>, /// Element run only for deployment post processing value. pub run_only_for_deployment_postprocessing: bool, - // ---- - kind: PBXBuildPhaseKind, - inner: Option, - objects: WeakPBXObjectCollection, + /// Build Phase Kind + pub kind: PBXBuildPhaseKind, + /// inner (Some if PBXBuildPhase is PBXShellScriptBuildPhase) + pub inner: Option>, } -impl PBXBuildPhase { +impl<'a> PBXBuildPhase<'a> { const DEFAULT_BUILD_ACTION_MASK: isize = 2_147_483_647; - - /// Get Build files that the build phase include - pub fn files(&self) -> Option> { - // objects from file_references?.objects() - todo!() - } - - /// set file references. - pub fn set_file_references( - &mut self, - references: Option>, - ) -> Option> { - std::mem::replace(&mut self.file_references, references) - } - - /// Add file_reference - pub fn add_file_reference(&mut self, reference: String) { - let mut file_references = self.file_references.take().unwrap_or_default(); - file_references.insert(reference); - self.file_references = file_references.into(); - } -} - -impl PBXObjectExt for PBXBuildPhase { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result - where - Self: Sized, - { - let kind = value - .try_remove_kind("isa")? - .try_into_build_phase_kind() - .unwrap(); - - Ok(Self { - build_action_mask: value - .try_remove_number("buildActionMask") - .unwrap_or_else(|_| Self::DEFAULT_BUILD_ACTION_MASK), - file_references: value - .remove_vec("files") - .map(|v| v.try_into_vec_strings().ok()) - .flatten() - .map(|v| HashSet::from_iter(v)), - input_file_list_paths: value - .remove_vec("inputFileListPaths") - .map(|v| v.try_into_vec_strings().ok()) - .flatten(), - output_file_list_paths: value - .remove_vec("outputFileListPaths") - .map(|v| v.try_into_vec_strings().ok()) - .flatten(), - run_only_for_deployment_postprocessing: value - .remove_number("runOnlyForDeploymentPostprocessing") - .map(|v| v == 1) - .unwrap_or_default(), - objects, - inner: if kind.is_run_script() { - Some(PBXObjectExt::from_hashmap(value, Default::default())?) - } else { - None - }, - kind, - }) - } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } -} - -impl PBXBuildPhase { - /// Get inner script representation - pub fn get_inner(&self) -> Option<&PBXShellScriptBuildPhase> { - self.inner.as_ref() - } - - /// Get mutable inner script representation - pub fn get_inner_mut(&mut self) -> Option<&mut PBXShellScriptBuildPhase> { - self.inner.as_mut() - } - /// Whether build phase is PBXSourcesBuildPhase pub fn is_sources(&self) -> bool { self.kind.is_sources() @@ -147,3 +68,59 @@ impl PBXBuildPhase { self.kind.is_carbon_resources() } } + +impl<'a> AsPBXObject<'a> for PBXBuildPhase<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> anyhow::Result + where + Self: Sized + 'a, + { + let kind = value + .try_get_kind("isa")? + .as_pbx_build_phase() + .unwrap() + .clone(); + + Ok(Self { + id, + build_action_mask: value + .try_get_number("buildActionMask") + .map(|v| v.clone()) + .unwrap_or_else(|_| Self::DEFAULT_BUILD_ACTION_MASK), + files: value + .get_vec("files") + .and_then(|vec| { + Some( + vec.as_vec_strings() + .iter() + .flat_map(|&k| objects.get(k)) + .collect::>(), + ) + }) + .unwrap_or_default(), + input_file_list_paths: value + .get_vec("inputFileListPaths") + .map(|v| v.as_vec_strings()), + output_file_list_paths: value + .get_vec("outputFileListPaths") + .map(|v| v.as_vec_strings()), + run_only_for_deployment_postprocessing: value + .get_number("runOnlyForDeploymentPostprocessing") + .map(|v| v == &1) + .unwrap_or_default(), + inner: if kind.is_run_script() { + Some(AsPBXObject::as_pbx_object( + Default::default(), + value, + objects, + )?) + } else { + None + }, + kind, + }) + } +} diff --git a/src/pbxproj/object/build/phase/rule.rs b/src/pbxproj/object/build/phase/rule.rs index 22dc08a..48de430 100644 --- a/src/pbxproj/object/build/phase/rule.rs +++ b/src/pbxproj/object/build/phase/rule.rs @@ -1,65 +1,59 @@ +use anyhow::Result; + use crate::pbxproj::*; /// [`PBXObject`] specifying how to transform input file(s) to an output file(s). /// /// [`PBXObject`]: crate::pbxproj::PBXObject #[derive(Debug, derive_new::new)] -pub struct PBXBuildRule { +pub struct PBXBuildRule<'a> { + /// ID Reference + pub id: String, /// Element compiler spec. - pub compiler_spec: Option, + pub compiler_spec: Option<&'a String>, /// Element file patterns. - pub file_patterns: Option, + pub file_patterns: Option<&'a String>, /// Element file type. - pub file_type: Option, + pub file_type: Option<&'a String>, /// Element is editable. pub is_editable: Option, /// Element name. - pub name: Option, + pub name: Option<&'a String>, /// Element output files. - pub output_files: Option>, + pub output_files: Option>, /// Element input files. - pub input_files: Option>, + pub input_files: Option>, /// Element output files compiler flags. - pub output_files_compiler_flags: Option>, + pub output_files_compiler_flags: Option>, /// Element script. - pub script: Option, + pub script: Option<&'a String>, /// Element run once per architecture. pub run_once_per_architecture: Option, - objects: WeakPBXObjectCollection, } -impl PBXObjectExt for PBXBuildRule { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result +impl<'a> AsPBXObject<'a> for PBXBuildRule<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + _objects: &'a PBXObjectCollection, + ) -> Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - compiler_spec: value.remove_string("compilerSpec"), - file_patterns: value.remove_string("filePatterns"), - file_type: value.remove_string("fileType"), - is_editable: value.remove_number("isEditable").map(|n| n == 1), - name: value.remove_string("name"), - output_files: value - .remove_vec("outputFiles") - .map(|v| v.try_into_vec_strings().ok()) - .flatten(), - input_files: value - .remove_vec("inputFiles") - .map(|v| v.try_into_vec_strings().ok()) - .flatten(), + id, + compiler_spec: value.get_string("compilerSpec"), + file_patterns: value.get_string("filePatterns"), + file_type: value.get_string("fileType"), + is_editable: value.get_number("isEditable").map(|n| n == &1), + name: value.get_string("name"), + output_files: value.get_vec("outputFiles").map(|v| v.as_vec_strings()), + input_files: value.get_vec("inputFiles").map(|v| v.as_vec_strings()), output_files_compiler_flags: value - .remove_vec("outputFilesCompilerFlags") - .map(|v| v.try_into_vec_strings().ok()) - .flatten(), - script: value.remove_string("script"), - run_once_per_architecture: value - .remove_number("runOncePerArchitecture") - .map(|n| n == 1), - objects, + .get_vec("outputFilesCompilerFlags") + .map(|v| v.as_vec_strings()), + script: value.get_string("script"), + run_once_per_architecture: value.get_number("runOncePerArchitecture").map(|n| n == &1), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/build/phase/script.rs b/src/pbxproj/object/build/phase/script.rs index eb3bf18..96dfec6 100644 --- a/src/pbxproj/object/build/phase/script.rs +++ b/src/pbxproj/object/build/phase/script.rs @@ -4,54 +4,49 @@ use crate::pbxproj::*; /// /// [`RunScript`]: crate::pbxproj::PBXBuildPhaseKind::RunScript #[derive(Debug, derive_new::new)] -pub struct PBXShellScriptBuildPhase { +pub struct PBXShellScriptBuildPhase<'a> { /// Build phase name. - pub name: Option, + pub name: Option<&'a String>, /// Input paths - pub input_paths: Vec, + pub input_paths: Vec<&'a String>, /// Output paths - pub output_paths: Vec, + pub output_paths: Vec<&'a String>, /// Path to the shell. - pub shell_path: Option, + pub shell_path: Option<&'a String>, /// Shell script. - pub shell_script: Option, + pub shell_script: Option<&'a String>, /// Show environment variables in the logs. pub show_env_vars_in_log: bool, /// Force script to run in all incremental builds. pub always_out_of_date: bool, /// Path to the discovery .d dependency file - pub dependency_file: Option, + pub dependency_file: Option<&'a String>, } -impl PBXObjectExt for PBXShellScriptBuildPhase { - fn from_hashmap( - mut value: PBXHashMap, - _objects: WeakPBXObjectCollection, +impl<'a> AsPBXObject<'a> for PBXShellScriptBuildPhase<'a> { + fn as_pbx_object( + _id: String, + value: &'a PBXHashMap, + _objects: &'a PBXObjectCollection, ) -> anyhow::Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - name: value.remove_string("name"), - input_paths: value.try_remove_vec("inputPaths")?.try_into_vec_strings()?, - output_paths: value - .try_remove_vec("outputPaths")? - .try_into_vec_strings()?, - shell_path: value.remove_string("shellPath"), - shell_script: value.remove_string("shellScript"), + name: value.get_string("name"), + input_paths: value.try_get_vec("inputPaths")?.as_vec_strings(), + output_paths: value.try_get_vec("outputPaths")?.as_vec_strings(), + shell_path: value.get_string("shellPath"), + shell_script: value.get_string("shellScript"), show_env_vars_in_log: value - .remove_number("runOnlyForDeploymentPostprocessing") - .map(|v| v == 1) + .get_number("runOnlyForDeploymentPostprocessing") + .map(|v| v == &1) .unwrap_or_else(|| true), always_out_of_date: value - .remove_number("alwaysOutOfDate") - .map(|v| v == 1) + .get_number("alwaysOutOfDate") + .map(|v| v == &1) .unwrap_or_else(|| false), - dependency_file: value.remove_string("dependencyFile"), + dependency_file: value.get_string("dependencyFile"), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/collection.rs b/src/pbxproj/object/collection.rs index d9dc228..6e9d8ed 100644 --- a/src/pbxproj/object/collection.rs +++ b/src/pbxproj/object/collection.rs @@ -1,261 +1,255 @@ -use md5::Digest; - -use super::*; -use std::{ - cell::{Ref, RefCell}, - collections::HashMap, - rc::{Rc, Weak}, -}; - -/// An alias for weak reference of [`PBXObjectCollection`] -pub type WeakPBXObjectCollection = Weak>; +// use md5::Digest; +// use rand::distributions::Alphanumeric; +// use rand::{thread_rng, Rng}; +use crate::pbxproj::*; +use anyhow::Result; +use std::collections::HashMap; /// [`PBXObject`] storage with convenient helper methods #[derive(Default, Debug, derive_new::new, derive_deref_rs::Deref)] -pub struct PBXObjectCollection(pub(crate) HashMap); - -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; +pub struct PBXObjectCollection(pub(crate) HashMap); + +/// Get PBXObject from PBXHashMap and PBXObjectCollection +pub trait AsPBXObject<'a> { + /// create a pbx object out of given value + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> Result + where + Self: Sized + 'a; +} -/// TODO: make collections a HashSet of PBXObject with identifier included? impl PBXObjectCollection { - /// Add new object. same as insert but it auto create id and returns it - pub fn push>(&mut self, object: O) -> String { - let data: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(20) - .map(char::from) - .collect(); - let mut hasher = md5::Md5::new(); - let ref mut buf = [0u8; 128]; - hasher.update(&data); - let hash = hasher.finalize(); - let id = base16ct::upper::encode_str(&hash, buf).unwrap()[..24].to_string(); - self.insert(id.clone(), object.into()); - id + /// Get T from collection + pub fn get<'a, T, S>(&'a self, key: S) -> Option + where + T: AsPBXObject<'a> + 'a, + S: AsRef, + { + self.0.get(key.as_ref()).and_then(|value| { + AsPBXObject::as_pbx_object(key.as_ref().to_string(), value, self).ok() + }) } - /// Get PBXTarget by reference - pub fn get_target<'a>(&'a self, reference: &str) -> Option>> { - self.get(reference)?.as_pbx_target().map(|r| r.clone()) + /// Get T from collection + pub fn try_get<'a, T, S>(&'a self, key: S) -> Result + where + T: AsPBXObject<'a> + 'a, + S: AsRef + std::fmt::Debug, + { + self.get(key.as_ref()) + .ok_or_else(|| anyhow::anyhow!("{key:?} doesn't exists!")) + } + + /// Get PBXObject a vector of type T + pub fn get_vec<'a, T, I, S>(&'a self, keys: I) -> Vec + where + T: AsPBXObject<'a> + 'a, + I: IntoIterator + Send, + S: AsRef, + { + keys.into_iter() + .flat_map(|key| self.get(key.as_ref())) + .collect::>() } - /// Get PBXBuildPhase by reference - pub fn get_build_phase<'a>(&'a self, reference: &str) -> Option>> { - self.get(reference)?.as_pbx_build_phase().map(|r| r.clone()) + /// Get vector by vector of T by predict + pub fn get_vec_by<'a, T: AsPBXObject<'a> + 'a>( + &'a self, + predict: fn(&(&String, &PBXHashMap)) -> bool, + ) -> Vec { + self.iter() + .filter(predict) + .flat_map(|(k, _)| self.get(k)) + .collect::>() } - /// Get PBXBuildFile by reference - pub fn get_build_file<'a>(&'a self, reference: &str) -> Option>> { - self.get(reference)?.as_pbx_build_file().map(|r| r.clone()) + /// Get all PBXTarget + pub fn targets<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_pbx_target()) + .unwrap_or_default() + }) } - /// Get all PBXBuildPhase - pub fn build_phases<'a>(&'a self) -> Vec<(String, Rc>)> { - self.iter() - .filter(|o| o.1.is_pbx_build_phase()) - .map(|(k, o)| (k.clone(), o.as_pbx_build_phase().unwrap().clone())) - .collect() + /// Get all PBXProject + pub fn projects<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_pbx_project()) + .unwrap_or_default() + }) } - /// Get all PBXTargets - pub fn targets<'a>(&'a self) -> Vec<(String, Rc>)> { - self.iter() - .filter(|o| o.1.is_pbx_target()) - .map(|(k, o)| (k.clone(), o.as_pbx_target().unwrap().clone())) - .collect() + /// Get all build phases + pub fn build_phases<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_pbx_build_phase()) + .unwrap_or_default() + }) } - pub(crate) fn get_fs_references<'a>( - &'a self, - predict: fn(Ref) -> bool, - ) -> impl Iterator>)> + 'a { - self.iter() - .filter(move |o| { - if let Some(fs_reference) = o.1.as_pbxfs_reference() { - predict(fs_reference.borrow()) - } else { - false - } - }) - .map(|(k, o)| (k.clone(), o.as_pbxfs_reference().unwrap().clone())) + /// Get all build phases + pub fn build_files<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_pbx_build_file()) + .unwrap_or_default() + }) } - pub(crate) fn fs_references<'a>(&'a self) -> Vec<(String, Rc>)> { - self.iter() - .map(|(k, o)| Some((k.clone(), o.as_pbxfs_reference()?.clone()))) - .flatten() - .collect() + /// Get all build phases + pub fn build_rules<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_pbx_build_rule()) + .unwrap_or_default() + }) } - /// Get all PBXGroup - pub fn groups<'a>(&'a self) -> Vec<(String, Rc>)> { - self.get_fs_references(|fs_reference| fs_reference.is_group()) - .collect() + /// Get all source code files + pub fn files<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| { + k.is_pbx_fsreference() + && k.as_pbxfs_reference() + .map(|r| r.is_file()) + .unwrap_or_default() + }) + .unwrap_or_default() + }) } - /// Get PBXGroup with by name or path - pub fn get_group_by_name_or_path<'a, S: AsRef>( + /// Get all groups + pub fn groups<'a>(&'a self) -> Vec> { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| { + k.is_pbx_fsreference() + && k.as_pbxfs_reference() + .map(|r| r.is_group()) + .unwrap_or_default() + }) + .unwrap_or_default() + }) + } + + /// Get All XCSwiftPackageProductDependency Objects + pub fn swift_package_product_dependencies<'a>( &'a self, - name_or_path: S, - ) -> Option<(String, Rc>)> { - let name = name_or_path.as_ref(); - self.groups().into_iter().find(|(_, o)| { - let group = o.borrow(); - if let Some(n) = group.name() { - return n == name; - } else if let Some(p) = group.path() { - return p == name; - } else { - false - } + ) -> Vec { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_xc_swift_package_product_dependency()) + .unwrap_or_default() }) } - /// Get all PBXProject - pub fn projects<'a>(&'a self) -> Vec<(String, Rc>)> { - self.iter() - .filter(|o| o.1.is_pbx_project()) - .map(|(k, o)| (k.clone(), o.as_pbx_project().unwrap().clone())) - .collect() + /// Get All XCRemoteSwiftPackageReference Objects + pub fn swift_package_references<'a>(&'a self) -> Vec { + self.get_vec_by(|(_, v)| { + v.get_kind("isa") + .map(|k| k.is_xc_remote_swift_package_reference()) + .unwrap_or_default() + }) } - /// Get all files - pub fn files<'a>(&'a self) -> Vec<(String, Rc>)> { - self.get_fs_references(|fs_reference| fs_reference.is_file()) - .collect() + /// Get PBXTarget + pub fn get_target<'a>(&'a self, key: &str) -> Option { + self.get(key) } - /// Get all PBXBuildFile - pub fn build_files<'a>(&'a self) -> Vec<(String, Rc>)> { - self.iter() - .filter(|o| o.1.is_pbx_build_file()) - .map(|(k, o)| (k.clone(), o.as_pbx_build_file().unwrap().clone())) - .collect() + /// Get PBXBuildPhase + pub fn get_build_phase<'a>(&'a self, key: &str) -> Option { + self.get(key) } - /// Get All XCSwiftPackageProductDependency Objects - pub fn swift_package_product_dependencies<'a>( - &'a self, - ) -> Vec<(String, Rc>)> { - self.iter() - .map(|(k, v)| { - Some(( - k.clone(), - v.as_xc_swift_package_product_dependency()?.clone(), - )) - }) - .flatten() - .collect::>() + /// Get PBXBuildFile + pub fn get_build_file<'a>(&'a self, key: &str) -> Option { + self.get(key) } - /// Get All XCRemoteSwiftPackageReference Objects - pub fn swift_package_references<'a>( - &'a self, - ) -> Vec<(String, Rc>)> { - self.iter() - .map(|(k, v)| Some((k.clone(), v.as_xc_remote_swift_package_reference()?.clone()))) - .flatten() - .collect::>() + /// Get PBXBuildRule + pub fn get_build_rule<'a>(&'a self, key: &str) -> Option { + self.get(key) } - /// Get XCSwiftPackageProductDependency by reference - pub fn get_swift_package_product_dependency<'a>( - &'a self, - object_reference: &str, - ) -> Option>> { - self.get(object_reference)? - .as_xc_swift_package_product_dependency() - .map(|r| r.clone()) + /// Get PBXProject + pub fn get_project<'a>(&'a self, key: &str) -> Option { + self.get(key) } - /// Get XCSwiftPackageProductDependency by reference - pub fn get_swift_package_reference<'a>( - &'a self, - object_reference: &str, - ) -> Option>> { - self.get(object_reference)? - .as_xc_remote_swift_package_reference() - .map(|r| r.clone()) + /// Get all files + pub fn get_file<'a>(&'a self, key: &str) -> Option { + let fs_ref = self.get::(key)?; + if fs_ref.is_file() { + Some(fs_ref) + } else { + None + } } - /// Get PBXTarget from a vec of references - pub fn get_targets_from_references<'a>( - &'a self, - references: &Vec, - ) -> Vec<(String, Rc>)> { - references - .iter() - .map(|id| Some((id.clone(), self.get_target(id)?))) - .flatten() - .collect() + /// Get all files + pub fn get_group<'a>(&'a self, key: &str) -> Option { + let fs_ref = self.get::(key)?; + if fs_ref.is_group() { + Some(fs_ref) + } else { + None + } } - /// Get PBXTarget by the target name - pub fn get_target_by_name<'a>( - &'a self, - target_name: &'a str, - ) -> Option<(String, Rc>)> { - self.iter() - .find(|(_, o)| { - if let Some(target) = o.as_pbx_target() { - if let Some(name) = target.borrow().name.as_ref() { - name == target_name - } else { - false - } - } else { - false - } - }) - .map(|(key, o)| (key.clone(), o.as_pbx_target().unwrap().clone())) + /// Get fs object + pub fn get_fs_object<'a>(&'a self, key: &str) -> Option { + self.get(key) } - /// Get XCRemoteSwiftPackageReference from a vec of references - pub fn get_swift_package_reference_from_references<'a>( + /// Get PBXGroup with by name or path + pub fn get_group_by_name_or_path<'a, S: AsRef>( &'a self, - references: &Vec, - ) -> Vec<(String, Rc>)> { - references - .iter() - .map(|reference| { - Some(( - reference.clone(), - self.get_swift_package_reference(reference)?, - )) - }) - .flatten() - .collect() + name_or_path: S, + ) -> Option { + let name = name_or_path.as_ref(); + self.groups().into_iter().find(|o| { + if let Some(n) = o.name { + return n == name; + } else if let Some(p) = o.path { + return p == name; + } else { + false + } + }) } - /// Get PBXBuildPhase from a vec of references - pub fn get_build_phases_from_reference<'a>( + /// Get XCSwiftPackageProductDependency by reference + pub fn get_swift_package_product_dependency<'a>( &'a self, - references: &Vec, - ) -> Vec<(String, Rc>)> { - references - .iter() - .map(|reference| Some((reference.clone(), self.get_build_phase(reference)?))) - .flatten() - .collect() + key: &str, + ) -> Option { + self.get(key) } - /// Get XCSwiftPackageProductDependency form a given target reference - pub fn get_product_dependency_from_target_reference<'a>( + /// Get XCSwiftPackageProductDependency by reference + pub fn get_swift_package_reference<'a>( &'a self, - target_reference: &str, - ) -> Option<(String, Rc>)> { - self.swift_package_product_dependencies() - .into_iter() - .find(|(_, p)| { - p.borrow() - .package_reference() - .map(|v| v == target_reference) - .unwrap_or_default() - }) + key: &str, + ) -> Option { + self.get(key) } - pub(crate) fn set_inner(&mut self, map: HashMap) { - self.0 = map; + /// Get PBXTarget by the target name + pub fn get_target_by_name<'a>(&'a self, name: &'a str) -> Option { + self.targets().into_iter().find(|target| { + if let Some(target_name) = target.name { + target_name == name + } else { + false + } + }) } } diff --git a/src/pbxproj/object/container_item_proxy.rs b/src/pbxproj/object/container_item_proxy.rs new file mode 100644 index 0000000..98817f6 --- /dev/null +++ b/src/pbxproj/object/container_item_proxy.rs @@ -0,0 +1,70 @@ +use crate::pbxproj::*; + +/// [`PBXObject`] that reference another object used by [`PBXTargetDependency`] +/// +/// [`PBXObject`]: crate::pbxproj::PBXObject +/// [`PBXTargetDependency`]: crate::pbxproj::PBXTargetDependency +#[derive(Debug, derive_new::new)] +pub struct PBXContainerItemProxy<'a> { + /// ID Reference + pub id: String, + /// The object is a reference to a PBXProject, if proxy is for the object located in current .xcodeproj, otherwise PBXFileReference. + pub container_portal_reference: &'a String, + /// Element proxy type. + pub proxy_type: Option, + /// Element remote global ID reference. ID of the proxied object. + pub remote_global_id_reference: Option<&'a String>, + /// Element remote info. + pub remote_info: Option<&'a String>, +} + +impl<'a> AsPBXObject<'a> for PBXContainerItemProxy<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + _objects: &'a PBXObjectCollection, + ) -> anyhow::Result + where + Self: Sized + 'a, + { + Ok(Self { + id, + container_portal_reference: value.try_get_string("containerPortal")?, + proxy_type: value + .get_value("proxyType") + .map(|v| v.try_into().ok()) + .flatten(), + remote_global_id_reference: value.get_string("remoteGlobalIdString"), + remote_info: value.get_string("remoteInfo"), + }) + } +} + +#[derive(Debug, PartialEq, Eq)] +/// [`PBXContainerItemProxy`] Type +pub enum PBXProxyType { + /// Native Target + NativeTarget, + /// Reference + Reference, + /// Other + Other(u8), +} + +impl TryFrom<&PBXValue> for PBXProxyType { + type Error = anyhow::Error; + + fn try_from(value: &PBXValue) -> Result { + Ok( + match value + .as_number() + .ok_or_else(|| anyhow::anyhow!("No pbx product type foudn"))? + { + 1 => Self::NativeTarget, + 2 => Self::Reference, + o => Self::Other(*o as u8), + }, + ) + } +} + diff --git a/src/pbxproj/object/file/container_item.rs b/src/pbxproj/object/file/container_item.rs deleted file mode 100644 index ef47324..0000000 --- a/src/pbxproj/object/file/container_item.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::pbxproj::*; - -/// Item Proxy type used in [`PBXContainerItemProxy`] -#[derive(Debug)] -pub enum PBXProxyType { - /// Native Target - NativeTarget, - /// Reference - Reference, - /// Other - Other(u8), -} - -impl TryFrom for PBXProxyType { - type Error = anyhow::Error; - - fn try_from(value: PBXValue) -> Result { - Ok(match value.try_into_number()? { - 1 => Self::NativeTarget, - 2 => Self::Reference, - o => Self::Other(o as u8), - }) - } -} - -/// [`PBXObject`] that reference another object used by [`PBXTargetDependency`] -/// -/// [`PBXObject`]: crate::pbxproj::PBXObject -/// [`PBXTargetDependency`]: crate::pbxproj::PBXTargetDependency -#[derive(Debug, derive_new::new)] -pub struct PBXContainerItemProxy { - /// The object is a reference to a PBXProject, if proxy is for the object located in current .xcodeproj, otherwise PBXFileReference. - container_portal_reference: String, - /// Element proxy type. - pub proxy_type: Option, - /// Element remote global ID reference. ID of the proxied object. - remote_global_id_reference: Option, - /// Element remote info. - pub remote_info: Option, - objects: WeakPBXObjectCollection, -} - -impl PBXObjectExt for PBXContainerItemProxy { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result - where - Self: Sized, - { - Ok(Self { - container_portal_reference: value.try_remove_string("containerPortal")?, - proxy_type: value - .remove_value("proxyType") - .map(|v| v.try_into().ok()) - .flatten(), - remote_global_id_reference: value.remove_string("remoteGlobalIdString"), - remote_info: value.remove_string("remoteInfo"), - objects, - }) - } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } -} diff --git a/src/pbxproj/object/file/mod.rs b/src/pbxproj/object/file/mod.rs deleted file mode 100644 index 17e3ace..0000000 --- a/src/pbxproj/object/file/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod container_item; -pub use container_item::*; diff --git a/src/pbxproj/object/fs/kind.rs b/src/pbxproj/object/fs/kind.rs index 8369bec..a801cac 100644 --- a/src/pbxproj/object/fs/kind.rs +++ b/src/pbxproj/object/fs/kind.rs @@ -1,40 +1,16 @@ use derive_is_enum_variant::is_enum_variant; -#[derive(Debug, PartialEq, Eq, is_enum_variant, Ord, PartialOrd)] -/// [`PBXFSReferenceKind`] group abstraction kind -pub enum PBXGroupKind { - /// PBXGroup - FileGroup, - /// XCVersionGroup - VersionGroup, - /// XCVersionGroup - VariantGroup, -} - -impl PBXGroupKind { - /// Return string representation of [`PBXGroupKind`] - pub fn as_isa(&self) -> &str { - match self { - PBXGroupKind::FileGroup => "PBXFileGroup", - PBXGroupKind::VersionGroup => "XCVersionGroup", - PBXGroupKind::VariantGroup => "PBXVariantGroup", - } - } -} - -impl Default for PBXGroupKind { - fn default() -> Self { - Self::FileGroup - } -} - -#[derive(Debug, PartialEq, Eq, is_enum_variant, Ord, PartialOrd)] +#[derive(Clone, Debug, PartialEq, Eq, is_enum_variant, Ord, PartialOrd)] /// [`PBXFSReference`] abstraction kind /// /// [`PBXFSReference`]: crate::pbxproj::PBXFSReference pub enum PBXFSReferenceKind { - /// Group - Group(PBXGroupKind), + /// File Group + FileGroup, + /// Version Group + VersionGroup, + /// Variant Group + VariantGroup, /// PBXFileReference File, } @@ -43,14 +19,20 @@ impl PBXFSReferenceKind { /// Return string representation compatible with pbxproj pub fn as_isa(&self) -> &str { match self { - PBXFSReferenceKind::Group(group_kind) => group_kind.as_isa(), + PBXFSReferenceKind::FileGroup => "PBXFileGroup", + PBXFSReferenceKind::VersionGroup => "XCVersionGroup", + PBXFSReferenceKind::VariantGroup => "PBXVariantGroup", PBXFSReferenceKind::File => "PBXFileReference", } } + /// Returns group if kind is FileGroup, VersionGroup, or VariantGroup, + pub fn is_group(&self) -> bool { + self.is_file_group() || self.is_version_group() || self.is_variant_group() + } } impl Default for PBXFSReferenceKind { fn default() -> Self { - Self::Group(PBXGroupKind::default()) + Self::FileGroup } } diff --git a/src/pbxproj/object/fs/mod.rs b/src/pbxproj/object/fs/mod.rs index 850fd21..426d7bd 100644 --- a/src/pbxproj/object/fs/mod.rs +++ b/src/pbxproj/object/fs/mod.rs @@ -1,88 +1,165 @@ -mod full_path; mod kind; -mod obj; -mod setget; mod source_tree; - -use crate::xcode::xcode_file_type; - use super::*; -use std::{ - cell::RefCell, - collections::HashSet, - path::{Path, PathBuf}, - rc::{Rc, Weak}, -}; - -use anyhow::{bail, Result}; +use crate::pbxproj::PBXHashMap; +use anyhow::Result; + pub use kind::*; pub use source_tree::*; -use tap::Pipe; + +// mod full_path; +// use crate::xcode::xcode_file_type; /// Abstraction over `PBXFileReference`, `PBXGroup`, `PBXVariantGroup`, and `XCVersionGroup` #[derive(Debug, Default)] -pub struct PBXFSReference { +pub struct PBXFSReference<'a> { + /// ID Reference + pub id: String, /// Element source tree. - source_tree: Option, + pub source_tree: PBXSourceTree, /// Element path. - path: Option, + pub path: Option<&'a String>, /// Element name. - name: Option, + pub name: Option<&'a String>, /// Element include in index. - include_in_index: Option, + pub include_in_index: Option, /// Element uses tabs. - uses_tabs: Option, + pub uses_tabs: Option, /// Element indent width. - indent_width: Option, + pub indent_width: Option<&'a isize>, /// Element tab width. - tab_width: Option, + pub tab_width: Option<&'a isize>, /// Element wraps lines. - wraps_lines: Option, - /// Element parent. - kind: PBXFSReferenceKind, - /// Group children references (only relevant to PBX*Group) - children_references: Option>, + pub wraps_lines: Option, + /// Element Kind. + pub kind: PBXFSReferenceKind, /// Text encoding of file content (only relevant to PBXFileReference) - file_encoding: Option, + pub file_encoding: Option<&'a isize>, /// User-specified file type. use `last_known_file_type` instead. (only relevant to PBXFileReference) - explicit_file_type: Option, + pub explicit_file_type: Option<&'a String>, /// Derived file type. For a file named "foo.swift" this value would be "sourcecode.swift" (only relevant to PBXFileReference) - last_known_file_type: Option, + pub last_known_file_type: Option<&'a String>, /// Line ending type for the file (only relevant to PBXFileReference) - line_ending: Option, + pub line_ending: Option<&'a isize>, /// Legacy programming language identifier (only relevant to PBXFileReference) - language_specification_identifier: Option, + pub language_specification_identifier: Option<&'a String>, /// Programming language identifier (only relevant to PBXFileReference) - xc_language_specification_identifier: Option, + pub xc_language_specification_identifier: Option<&'a String>, /// Plist organizational family identifier (only relevant to PBXFileReference) - plist_structure_definition_identifier: Option, + pub plist_structure_definition_identifier: Option<&'a String>, /// Current version. (only relevant for XCVersionGroup) - current_version_reference: Option, + pub current_version_reference: Option<&'a String>, /// Version group type. (only relevant for XCVersionGroup) - version_group_type: Option, + pub version_group_type: Option<&'a String>, + /// Parent ojbect + pub parent: Option>, + /// Group children (only relevant to PBX*Group!!) + pub children: Vec, +} - parent: Weak>, - pub(crate) objects: WeakPBXObjectCollection, +impl<'a> Eq for PBXFSReference<'a> {} +impl<'a> PartialEq for PBXFSReference<'a> { + fn eq(&self, other: &Self) -> bool { + self.kind == other.kind + && self.source_tree == other.source_tree + && self.path == other.path + && self.name == other.name + // && self.children_references == other.children_references + && self.current_version_reference == other.current_version_reference + && self.version_group_type == other.version_group_type + && self.include_in_index == other.include_in_index + && self.uses_tabs == other.uses_tabs + && self.indent_width == other.indent_width + && self.tab_width == other.tab_width + && self.wraps_lines == other.wraps_lines + && self.file_encoding == other.file_encoding + && self.explicit_file_type == other.explicit_file_type + && self.last_known_file_type == other.last_known_file_type + && self.line_ending == other.line_ending + && self.language_specification_identifier == other.language_specification_identifier + && self.xc_language_specification_identifier + == other.xc_language_specification_identifier + && self.plist_structure_definition_identifier + == other.plist_structure_definition_identifier + } } -impl PBXFSReference { - /// Get Group children. - /// WARN: This will return empty if self is of type file - pub fn children(&self) -> Vec>> { - if self.is_file() || self.children_references.is_none() { - return vec![]; - } - let objects = self.objects.upgrade().expect("Objects to valid reference"); - let objects = objects.borrow(); - self.children_references - .as_ref() - .unwrap() - .iter() - .map(|r| Some(objects.get(r)?.as_pbxfs_reference()?.clone())) - .flatten() - .collect::>() +impl<'a> AsPBXObject<'a> for PBXFSReference<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> Result + where + Self: Sized + 'a, + { + let kind = value + .try_get_kind("isa")? + .as_pbxfs_reference() + .ok_or_else(|| anyhow::anyhow!("isa isn't defined: trying to get `PBXFSReference`"))? + .clone(); + + Ok(Self { + id, + name: value.get_string("name"), + path: value.get_string("path"), + kind, + source_tree: value + .get_string("sourceTree") + .map(|s| s.as_str().into()) + .unwrap_or_default(), + include_in_index: value.get_number("includeInIndex").map(|v| v == &1), + uses_tabs: value.get_number("usesTabs").map(|v| v == &1), + indent_width: value.get_number("indentWidth"), + tab_width: value.get_number("tabWidth"), + wraps_lines: value.get_number("wrapsLines").map(|v| v == &1), + current_version_reference: value.get_string("currentVersion"), + parent: None, + file_encoding: value.get_number("fileEncoding"), + explicit_file_type: value.get_string("explicitFileType"), + last_known_file_type: value.get_string("lastKnownFileType"), + line_ending: value.get_number("lineEnding"), + language_specification_identifier: value.get_string("languageSpecificationIdentifier"), + xc_language_specification_identifier: value + .get_string("xcLanguageSpecificationIdentifier"), + plist_structure_definition_identifier: value + .get_string("xcLanguageSpecificationIdentifier"), + version_group_type: value.get_string("versioGroupType"), + children: value + .get_vec("children") + .map(|v| objects.get_vec(v.as_vec_strings())) + .unwrap_or_default(), + }) } +} +impl<'a> PBXFSReference<'a> { + /// Check whether this fs reference is group + pub fn is_group(&self) -> bool { + self.kind.is_group() + } + + /// Check whether this fs reference is group and is file group + pub fn is_file_group(&self) -> bool { + self.kind.is_file_group() + } + + /// Check whether this fs reference is group and is version group + pub fn is_version_group(&self) -> bool { + self.kind.is_version_group() + } + + /// Check whether this fs reference is group and is variant group + pub fn is_varient_group(&self) -> bool { + self.kind.is_variant_group() + } + + /// Check whether this fs reference is file + pub fn is_file(&self) -> bool { + self.kind.is_file() + } + + /* /// Get group from children with given name /// /// NOTE: This will return None if self is file @@ -105,28 +182,9 @@ impl PBXFSReference { } }) } + */ - pub(crate) fn assign_parent_to_children(&self, this: Weak>) { - if self.is_group() { - self.children().into_iter().for_each(|o| { - let mut fs_reference = o.borrow_mut(); - fs_reference.parent = this.clone(); - fs_reference.assign_parent_to_children(Rc::downgrade(&o)) - }); - } - } - - /// Set the pbxfsreference's parent. - pub fn set_parent(&mut self, parent: Weak>) { - self.parent = parent; - } - - /// Get a reference to the pbxfsreference's parent. - #[must_use] - pub fn parent(&self) -> Option>> { - self.parent.upgrade() - } - + /* /// Get File from the group /// /// NOTE: This will return None if self is file @@ -147,7 +205,9 @@ impl PBXFSReference { } }) } + */ + /* /// Add file to a group /// /// NOTE: This will return None if self is file @@ -248,109 +308,83 @@ impl PBXFSReference { Ok(file_reference) } + */ } -impl Eq for PBXFSReference {} -impl PartialEq for PBXFSReference { - fn eq(&self, other: &Self) -> bool { - self.kind == other.kind - && self.source_tree == other.source_tree - && self.path == other.path - && self.name == other.name - && self.children_references == other.children_references - && self.current_version_reference == other.current_version_reference - && self.version_group_type == other.version_group_type - && self.include_in_index == other.include_in_index - && self.uses_tabs == other.uses_tabs - && self.indent_width == other.indent_width - && self.tab_width == other.tab_width - && self.wraps_lines == other.wraps_lines - && self.file_encoding == other.file_encoding - && self.explicit_file_type == other.explicit_file_type - && self.last_known_file_type == other.last_known_file_type - && self.line_ending == other.line_ending - && self.language_specification_identifier == other.language_specification_identifier - && self.xc_language_specification_identifier - == other.xc_language_specification_identifier - && self.plist_structure_definition_identifier - == other.plist_structure_definition_identifier - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - #[test] - fn get_parent() { - use crate::pbxproj::test_demo_file; - let project = test_demo_file!(demo1); - let main_group = project - .objects() - .projects() - .first() - .unwrap() - .1 - .borrow() - .main_group(); - - let main_group = main_group.borrow(); - let source_group = main_group.get_subgroup("Source").unwrap(); - let source_group = source_group.borrow(); - let parent = source_group.parent(); - - assert_eq!( - parent.unwrap().borrow().children_references(), - main_group.children_references() - ) - } - - #[test] - fn get_file() { - use crate::pbxproj::test_demo_file; - let project = test_demo_file!(demo1); - let source_group = project - .objects() - .get_group_by_name_or_path("Source") - .unwrap() - .1; - let source_group = source_group.borrow(); - let file = source_group.get_file("Log.swift"); - assert!(file.is_some()) - } - - #[test] - fn add_file_full_path() { - use crate::pbxproj::test_demo_file; - let root = PathBuf::from("/path/to/project"); - let project = test_demo_file!(demo1); - let source_group = project - .objects() - .get_group_by_name_or_path("Source") - .unwrap() - .1; - let mut source_group = source_group.borrow_mut(); - let file = source_group - .add_file( - root.join("Source").join("MyFile.swift").as_path(), - root.as_path(), - None, - ) - .unwrap(); - - assert_eq!(file.borrow().name(), Some(&String::from("MyFile.swift"))); - assert_eq!(file.borrow().path(), Some(&String::from("MyFile.swift"))); - - drop(file); - drop(source_group); - - let file = project.objects().files().into_iter().find(|(_, o)| { - o.borrow() - .path() - .map(|n| n == "MyFile.swift") - .unwrap_or_default() - }); - - assert!(file.is_some()); - } -} +// #[cfg(test)] +// mod tests { +// use std::path::PathBuf; + +// #[test] +// fn get_parent() { +// use crate::pbxproj::test_demo_file; +// let project = test_demo_file!(demo1); +// let main_group = project +// .objects() +// .projects() +// .first() +// .unwrap() +// .1 +// .borrow() +// .main_group(); + +// let main_group = main_group.borrow(); +// let source_group = main_group.get_subgroup("Source").unwrap(); +// let source_group = source_group.borrow(); +// let parent = source_group.parent(); + +// assert_eq!( +// parent.unwrap().borrow().children_references(), +// main_group.children_references() +// ) +// } + +// #[test] +// fn get_file() { +// use crate::pbxproj::test_demo_file; +// let project = test_demo_file!(demo1); +// let source_group = project +// .objects() +// .get_group_by_name_or_path("Source") +// .unwrap() +// .1; +// let source_group = source_group.borrow(); +// let file = source_group.get_file("Log.swift"); +// assert!(file.is_some()) +// } + +// #[test] +// fn add_file_full_path() { +// use crate::pbxproj::test_demo_file; +// let root = PathBuf::from("/path/to/project"); +// let project = test_demo_file!(demo1); +// let source_group = project +// .objects() +// .get_group_by_name_or_path("Source") +// .unwrap() +// .1; +// let mut source_group = source_group.borrow_mut(); +// let file = source_group +// .add_file( +// root.join("Source").join("MyFile.swift").as_path(), +// root.as_path(), +// None, +// ) +// .unwrap(); + +// assert_eq!(file.borrow().name(), Some(&String::from("MyFile.swift"))); +// assert_eq!(file.borrow().path(), Some(&String::from("MyFile.swift"))); + +// drop(file); +// drop(source_group); + +// let file = project.objects().files().into_iter().find(|(_, o)| { +// o.borrow() +// .path() +// .map(|n| n == "MyFile.swift") +// .unwrap_or_default() +// }); + +// assert!(file.is_some()); +// } +// } diff --git a/src/pbxproj/object/fs/obj.rs b/src/pbxproj/object/fs/obj.rs deleted file mode 100644 index 3a42181..0000000 --- a/src/pbxproj/object/fs/obj.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::pbxproj::PBXHashMap; -use anyhow::Result; - -use super::*; -impl PBXObjectExt for PBXFSReference { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> Result - where - Self: Sized, - { - let kind = value - .try_remove_kind("isa")? - .try_into_fs_reference_kind() - .unwrap(); - Ok(Self { - name: value.remove_string("name"), - path: value.remove_string("path"), - kind, - source_tree: value.remove_string("sourceTree").map(|s| s.into()), - include_in_index: value.remove_number("includeInIndex").map(|v| v == 1), - uses_tabs: value.remove_number("usesTabs").map(|v| v == 1), - indent_width: value.remove_number("indentWidth"), - tab_width: value.remove_number("tabWidth"), - wraps_lines: value.remove_number("wrapsLines").map(|v| v == 1), - current_version_reference: value.remove_string("currentVersion"), - children_references: value - .remove_vec("children") - .map(|v| v.try_into_vec_strings().ok().map(|v| HashSet::from_iter(v))) - .flatten(), - parent: Weak::new(), - file_encoding: value.remove_number("fileEncoding"), - explicit_file_type: value.remove_string("explicitFileType"), - last_known_file_type: value.remove_string("lastKnownFileType"), - line_ending: value.remove_number("lineEnding"), - language_specification_identifier: value - .remove_string("languageSpecificationIdentifier"), - xc_language_specification_identifier: value - .remove_string("xcLanguageSpecificationIdentifier"), - plist_structure_definition_identifier: value - .remove_string("xcLanguageSpecificationIdentifier"), - objects, - version_group_type: value.remove_string("versioGroupType"), - }) - } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } -} diff --git a/src/pbxproj/object/fs/setget.rs b/src/pbxproj/object/fs/setget.rs deleted file mode 100644 index 9bfe202..0000000 --- a/src/pbxproj/object/fs/setget.rs +++ /dev/null @@ -1,258 +0,0 @@ -use super::*; -impl PBXFSReference { - /// Check whether this fs reference is group - pub fn is_group(&self) -> bool { - self.kind.is_group() - } - - /// Check whether this fs reference is group and is file group - pub fn is_file_group(&self) -> bool { - if let PBXFSReferenceKind::Group(ref group) = self.kind { - group.is_file_group() - } else { - false - } - } - - /// Check whether this fs reference is group and is version group - pub fn is_version_group(&self) -> bool { - if let PBXFSReferenceKind::Group(ref group) = self.kind { - group.is_version_group() - } else { - false - } - } - - /// Check whether this fs reference is group and is variant group - pub fn is_varient_group(&self) -> bool { - if let PBXFSReferenceKind::Group(ref group) = self.kind { - group.is_variant_group() - } else { - false - } - } - - /// Check whether this fs reference is file - pub fn is_file(&self) -> bool { - self.kind.is_file() - } - - /// Get a reference to the reference's source tree. - #[must_use] - pub fn source_tree(&self) -> Option<&PBXSourceTree> { - self.source_tree.as_ref() - } - - /// Get a reference to the reference's path. - #[must_use] - pub fn path(&self) -> Option<&String> { - self.path.as_ref() - } - - /// Get a reference to the reference's name. - #[must_use] - pub fn name(&self) -> Option<&String> { - self.name.as_ref() - } - - /// Get the reference's include in index. - #[must_use] - pub fn include_in_index(&self) -> Option { - self.include_in_index - } - - /// Get the reference's uses tabs. - #[must_use] - pub fn uses_tabs(&self) -> Option { - self.uses_tabs - } - - /// Get the reference's indent width. - #[must_use] - pub fn indent_width(&self) -> Option { - self.indent_width - } - - /// Get the reference's tab width. - #[must_use] - pub fn tab_width(&self) -> Option { - self.tab_width - } - - /// Get the reference's wraps lines. - #[must_use] - pub fn wraps_lines(&self) -> Option { - self.wraps_lines - } - - /// Get a reference to the reference's kind. - #[must_use] - pub fn kind(&self) -> &PBXFSReferenceKind { - &self.kind - } - - /// Get a reference to the reference's children references. - /// WARN: Would panic if !self.is_group() - #[must_use] - pub fn children_references(&self) -> &HashSet { - &self.children_references.as_ref().unwrap() - } - - /// Get the reference's file encoding. - #[must_use] - pub fn file_encoding(&self) -> Option { - self.file_encoding - } - - /// Get a reference to the reference's explicit file type. - #[must_use] - pub fn explicit_file_type(&self) -> Option<&String> { - self.explicit_file_type.as_ref() - } - - /// Get a reference to the reference's last known file type. - #[must_use] - pub fn last_known_file_type(&self) -> Option<&String> { - self.last_known_file_type.as_ref() - } - - /// Get the reference's line ending. - #[must_use] - pub fn line_ending(&self) -> Option { - self.line_ending - } - - /// Get a reference to the reference's language specification identifier. - #[must_use] - pub fn language_specification_identifier(&self) -> Option<&String> { - self.language_specification_identifier.as_ref() - } - - /// Get a reference to the reference's xc language specification identifier. - #[must_use] - pub fn xc_language_specification_identifier(&self) -> Option<&String> { - self.xc_language_specification_identifier.as_ref() - } - - /// Get a reference to the reference's plist structure definition identifier. - #[must_use] - pub fn plist_structure_definition_identifier(&self) -> Option<&String> { - self.plist_structure_definition_identifier.as_ref() - } - - /// Get a reference to the reference's current version reference. - #[must_use] - pub fn current_version_reference(&self) -> Option<&String> { - self.current_version_reference.as_ref() - } - - /// Get a reference to the reference's version group type. - #[must_use] - pub fn version_group_type(&self) -> Option<&String> { - self.version_group_type.as_ref() - } - - /// Set the reference's source tree. - pub fn set_source_tree(&mut self, source_tree: PBXSourceTree) { - self.source_tree = Some(source_tree); - } - - /// Set the reference's path. - pub fn set_path(&mut self, path: Option) { - self.path = path; - } - - /// Set the reference's name. - pub fn set_name(&mut self, name: Option) { - self.name = name; - } - - /// Set the reference's include in index. - pub fn set_include_in_index(&mut self, include_in_index: Option) { - self.include_in_index = include_in_index; - } - - /// Set the reference's uses tabs. - pub fn set_uses_tabs(&mut self, uses_tabs: Option) { - self.uses_tabs = uses_tabs; - } - - /// Set the reference's indent width. - pub fn set_indent_width(&mut self, indent_width: Option) { - self.indent_width = indent_width; - } - - /// Set the reference's tab width. - pub fn set_tab_width(&mut self, tab_width: Option) { - self.tab_width = tab_width; - } - - /// Set the reference's wraps lines. - pub fn set_wraps_lines(&mut self, wraps_lines: Option) { - self.wraps_lines = wraps_lines; - } - - /// Set the reference's kind. - pub fn set_kind(&mut self, kind: PBXFSReferenceKind) { - self.kind = kind; - } - - /// Set the reference's children references. - pub fn set_children_references(&mut self, children_references: HashSet) { - self.children_references = Some(children_references); - } - - /// Set the reference's file encoding. - pub fn set_file_encoding(&mut self, file_encoding: Option) { - self.file_encoding = file_encoding; - } - - /// Set the reference's explicit file type. - pub fn set_explicit_file_type(&mut self, explicit_file_type: Option) { - self.explicit_file_type = explicit_file_type; - } - - /// Set the reference's last known file type. - pub fn set_last_known_file_type(&mut self, last_known_file_type: Option) { - self.last_known_file_type = last_known_file_type; - } - - /// Set the reference's line ending. - pub fn set_line_ending(&mut self, line_ending: Option) { - self.line_ending = line_ending; - } - - /// Set the reference's language specification identifier. - pub fn set_language_specification_identifier( - &mut self, - language_specification_identifier: Option, - ) { - self.language_specification_identifier = language_specification_identifier; - } - - /// Set the reference's xc language specification identifier. - pub fn set_xc_language_specification_identifier( - &mut self, - xc_language_specification_identifier: Option, - ) { - self.xc_language_specification_identifier = xc_language_specification_identifier; - } - - /// Set the reference's plist structure definition identifier. - pub fn set_plist_structure_definition_identifier( - &mut self, - plist_structure_definition_identifier: Option, - ) { - self.plist_structure_definition_identifier = plist_structure_definition_identifier; - } - - /// Set the reference's current version reference. - pub fn set_current_version_reference(&mut self, current_version_reference: Option) { - self.current_version_reference = current_version_reference; - } - - /// Set the reference's version group type. - pub fn set_version_group_type(&mut self, version_group_type: Option) { - self.version_group_type = version_group_type; - } -} diff --git a/src/pbxproj/object/kind.rs b/src/pbxproj/object/kind.rs index fc3617b..a5aa509 100644 --- a/src/pbxproj/object/kind.rs +++ b/src/pbxproj/object/kind.rs @@ -1,7 +1,7 @@ use derive_is_enum_variant::is_enum_variant; use enum_as_inner::EnumAsInner; -use super::{PBXBuildPhaseKind, PBXFSReferenceKind, PBXGroupKind}; +use super::{PBXBuildPhaseKind, PBXFSReferenceKind}; /// Representation of all Target kinds #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, is_enum_variant)] @@ -88,6 +88,30 @@ impl PBXObjectKind { Err(self) } } + + /// Returns `true` if the pbxobject kind is [`PBXTarget`]. + /// + /// [`PBXTarget`]: PBXObjectKind::PBXTarget + #[must_use] + pub fn is_pbx_target(&self) -> bool { + matches!(self, Self::PBXTarget(..)) + } + + /// Returns `true` if the pbxobject kind is [`PBXBuildPhase`]. + /// + /// [`PBXBuildPhase`]: PBXObjectKind::PBXBuildPhase + #[must_use] + pub fn is_pbx_build_phase(&self) -> bool { + matches!(self, Self::PBXBuildPhase(..)) + } + + /// Returns `true` if the pbxobject kind is [`PBXFSReference`]. + /// + /// [`PBXFSReference`]: PBXObjectKind::PBXFSReference + #[must_use] + pub fn is_pbx_fsreference(&self) -> bool { + matches!(self, Self::PBXFSReference(..)) + } } impl From<&str> for PBXObjectKind { @@ -99,7 +123,7 @@ impl From<&str> for PBXObjectKind { "PBXNativeTarget" => Self::PBXTarget(PBXTargetKind::Native), "PBXAggregateTarget" => Self::PBXTarget(PBXTargetKind::Aggregate), "PBXProject" => Self::PBXProject, - "PBXGroup" => Self::PBXFSReference(PBXFSReferenceKind::Group(PBXGroupKind::FileGroup)), + "PBXGroup" => Self::PBXFSReference(PBXFSReferenceKind::FileGroup), "PBXHeadersBuildPhase" => Self::PBXBuildPhase(PBXBuildPhaseKind::Headers), "PBXFrameworksBuildPhase" => Self::PBXBuildPhase(PBXBuildPhaseKind::Frameworks), "PBXResourcesBuildPhase" => Self::PBXBuildPhase(PBXBuildPhaseKind::Resources), @@ -109,14 +133,10 @@ impl From<&str> for PBXObjectKind { "PBXRezBuildPhase" => Self::PBXBuildPhase(PBXBuildPhaseKind::CarbonResources), "XCConfigurationList" => Self::XCConfigurationList, "PBXTargetDependency" => Self::PBXTargetDependency, - "PBXVariantGroup" => { - Self::PBXFSReference(PBXFSReferenceKind::Group(PBXGroupKind::VariantGroup)) - } + "PBXVariantGroup" => Self::PBXFSReference(PBXFSReferenceKind::VariantGroup), "XCBuildConfiguration" => Self::XCBuildConfiguration, "PBXContainerItemProxy" => Self::PBXContainerItemProxy, - "XCVersionGroup" => { - Self::PBXFSReference(PBXFSReferenceKind::Group(PBXGroupKind::VersionGroup)) - } + "XCVersionGroup" => Self::PBXFSReference(PBXFSReferenceKind::VersionGroup), "PBXBuildRule" => Self::PBXBuildRule, "XCRemoteSwiftPackageReference" => Self::XCRemoteSwiftPackageReference, "XCSwiftPackageProductDependency" => Self::XCSwiftPackageProductDependency, diff --git a/src/pbxproj/object/meta.rs b/src/pbxproj/object/meta.rs deleted file mode 100644 index 868e7e2..0000000 --- a/src/pbxproj/object/meta.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use super::*; -use crate::pbxproj::{PBXHashMap, PBXValue}; -use anyhow::Result; -use derive_is_enum_variant::is_enum_variant; -use enum_as_inner::EnumAsInner; -use enum_variant_macros::FromVariants; - -/// PBX Object Representation -#[derive(Clone, Debug, FromVariants, EnumAsInner, is_enum_variant)] -pub enum PBXObject { - /// Abstraction over `PBXAggregateTarget`, `PBXLegacyTarget`, and `PBXNativeTarget` - PBXTarget(Rc>), - /// Abstraction over Build phases - PBXBuildPhase(Rc>), - /// Abstraction over PBXFileReference and PBX*Group - PBXFSReference(Rc>), - /// A Kind for defining build configurations - XCBuildConfiguration(Rc>), - /// File referenced by a build phase, unique to each build phase. - PBXBuildFile(Rc>), - /// Specification of input transform to an output file(s). - PBXBuildRule(Rc>), - /// List of build configurations. - XCConfigurationList(Rc>), - /// A Kind representing Decoration for a target element - PBXContainerItemProxy(Rc>), - /// A Kind representing a build target that produces a binary content (application or library). - PBXProject(Rc>), - /// A Kind representing an abstract parent for specialized targets. - XCRemoteSwiftPackageReference(Rc>), - /// A Kind representing an abstract parent for specialized targets. - XCSwiftPackageProductDependency(Rc>), - /// A Kind representing a reference to other targets through content proxies. - PBXTargetDependency(Rc>), -} - -impl PBXObject { - /// Create new [`PBXObject`] - pub fn new(value: PBXValue, objects: WeakPBXObjectCollection) -> Result { - macro_rules! into { - ($var:ident, $map:ident, $objects:ident) => { - PBXObject::$var(Rc::new(RefCell::new(PBXObjectExt::from_hashmap( - $map, $objects, - )?))) - }; - } - - let map = value.try_into_object()?; - let kind = if let Some(kind) = map.get_kind("isa") { - kind - } else { - anyhow::bail!("isa field isn't defined: {map:#?}"); - }; - - Ok(match kind { - PBXObjectKind::XCBuildConfiguration => into!(XCBuildConfiguration, map, objects), - PBXObjectKind::PBXBuildFile => into!(PBXBuildFile, map, objects), - PBXObjectKind::PBXBuildRule => into!(PBXBuildRule, map, objects), - PBXObjectKind::XCConfigurationList => into!(XCConfigurationList, map, objects), - PBXObjectKind::PBXContainerItemProxy => into!(PBXContainerItemProxy, map, objects), - PBXObjectKind::PBXProject => into!(PBXProject, map, objects), - PBXObjectKind::XCRemoteSwiftPackageReference => { - into!(XCRemoteSwiftPackageReference, map, objects) - } - PBXObjectKind::XCSwiftPackageProductDependency => { - into!(XCSwiftPackageProductDependency, map, objects) - } - PBXObjectKind::PBXTargetDependency => into!(PBXTargetDependency, map, objects), - PBXObjectKind::PBXFSReference(_) => into!(PBXFSReference, map, objects), - - PBXObjectKind::PBXTarget(_) => into!(PBXTarget, map, objects), - PBXObjectKind::PBXBuildPhase(_) => into!(PBXBuildPhase, map, objects), - kind => anyhow::bail!("{kind:?} isn't supported"), - }) - } -} - -/// Process [`PBXObject`] -pub trait PBXObjectExt { - /// Create from [`PBXHashMap`] - fn from_hashmap(value: PBXHashMap, objects: WeakPBXObjectCollection) -> Result - where - Self: Sized; - /// generate [`PBXHashMap`] - fn to_hashmap(&self) -> PBXHashMap; -} diff --git a/src/pbxproj/object/mod.rs b/src/pbxproj/object/mod.rs index 2c0d596..640f8a0 100644 --- a/src/pbxproj/object/mod.rs +++ b/src/pbxproj/object/mod.rs @@ -1,11 +1,10 @@ mod build; -mod file; -mod meta; mod project; mod swift_package; mod target; mod collection; +mod container_item_proxy; mod fs; mod kind; mod product_type; @@ -15,8 +14,7 @@ pub use kind::*; pub use product_type::*; pub use build::*; -pub use file::*; -pub use meta::*; +pub use container_item_proxy::*; pub use project::*; pub use swift_package::*; diff --git a/src/pbxproj/object/project.rs b/src/pbxproj/object/project.rs index 5b8a944..eaceac4 100644 --- a/src/pbxproj/object/project.rs +++ b/src/pbxproj/object/project.rs @@ -1,566 +1,423 @@ -use anyhow::{bail, Result}; -use tap::Pipe; - use crate::pbxproj::*; -use std::{ - cell::{RefCell, RefMut}, - collections::HashMap, - rc::Rc, -}; +use std::collections::HashMap; /// [`PBXObject`] producing a binary content (application or library). /// /// [`PBXObject`]: crate::pbxproj::PBXObject #[derive(Debug, derive_new::new)] -pub struct PBXProject { +pub struct PBXProject<'a> { + /// ID Reference + pub id: String, /// Project name - pub name: Option, + pub name: Option<&'a String>, /// A string representation of the XcodeCompatibilityVersion. - pub compatibility_version: String, + pub compatibility_version: &'a String, /// The region of development. - pub development_region: Option, + pub development_region: Option<&'a String>, /// Whether file encodings have been scanned. - pub has_scanned_for_encodings: isize, + pub has_scanned_for_encodings: &'a isize, /// The known regions for localized files. - pub known_regions: Vec, + pub known_regions: Vec<&'a String>, /// The relative path of the project. - pub project_dir_path: String, + pub project_dir_path: &'a String, /// The relative root paths of the project. - pub project_roots: Vec, + pub project_roots: Vec<&'a String>, + /// Project main or root file group + pub main_group: PBXFSReference<'a>, /// Project attributes. - /// Target attributes will be merged into this - pub attributes: PBXHashMap, - /// Target attribute references. - target_attribute_references: Option, - /// Package references. - package_references: Option>, - /// Build configuration list reference. - build_configuration_list_reference: String, - /// The objects are a reference to a PBXTarget element. - target_references: Vec, - /// Project references. - project_references: Option>>, - /// The object is a reference to a PBXGroup element. - products_group_reference: Option, - /// The object is a reference to a PBXGroup element. - main_group_reference: String, - /// root - objects: WeakPBXObjectCollection, + pub attributes: &'a PBXHashMap, + /// Project's Targets attributes by target reference key + pub target_attributes: HashMap<&'a String, &'a PBXHashMap>, + /// Project's Package references. + pub packages: Vec>, + /// Project's Build configuration list + pub build_configuration_list: XCConfigurationList<'a>, + /// Project's targets + pub targets: Vec>, + // The object is a reference to a PBXGroup element. + // products_group_reference: Option, } -impl PBXProject { - /// Get packages that the project uses - #[must_use] - pub fn packages<'a>( - &self, - ) -> Option>)>> { - let package_references = self.package_references.as_ref()?; - let objects = self.objects.upgrade()?; - let objects = objects.borrow(); - Some(objects.get_swift_package_reference_from_references(package_references)) - } - - /// Get targets for given reference - #[must_use] - pub fn targets(&self) -> Vec<(String, Rc>)> { - let target_references = self.target_references.as_ref(); - if let Some(objects) = self.objects.upgrade() { - let objects = objects.borrow(); - objects.get_targets_from_references(target_references) - } else { - vec![] - } +impl<'a> AsPBXObject<'a> for PBXProject<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> anyhow::Result + where + Self: Sized + 'a, + { + let project_roots = + if let Some(roots) = value.get_vec("projectRoots").map(|v| v.as_vec_strings()) { + roots + } else if let Some(root) = value.get_string("projectRoot") { + vec![root] + } else { + vec![] + }; + + let attributes = value.try_get_object("attributes")?; + Ok(Self { + id, + name: value.get_string("name"), + compatibility_version: value.try_get_string("compatibilityVersion")?, + development_region: value.get_string("developmentRegion"), + + has_scanned_for_encodings: value.try_get_number("hasScannedForEncodings")?, + known_regions: value.try_get_vec("knownRegions")?.as_vec_strings(), + project_dir_path: value.try_get_string("projectDirPath")?, + project_roots, + target_attributes: attributes + .get_object("TargetAttributes") + .map(|v| { + v.iter() + .map(|(k, value)| Some((k, value.as_object()?))) + .flatten() + .collect::>() + }) + .unwrap_or_default(), + attributes, + packages: value + .get_vec("packageReferences") + .map(|v| objects.get_vec(v.as_vec_strings())) + .unwrap_or_default(), + build_configuration_list: value + .try_get_string("buildConfigurationList") + .and_then(|key| objects.try_get(key))?, + targets: value + .get_vec("targets") + .map(|v| objects.get_vec(v.as_vec_strings())) + .unwrap_or_default(), + main_group: value + .try_get_string("mainGroup") + .and_then(|key| objects.try_get(key))?, + // products_group_reference: value + // .get_value("productRefGroup") + // .map(|v| v.try_into().ok()) + // .flatten(), + }) } - +} +impl<'a> PBXProject<'a> { /// Returns the attributes of a given target. #[must_use] pub fn get_attributes_for_target_reference( &self, - target_reference: &str, - ) -> Option<&PBXHashMap> { - self.target_attribute_references - .as_ref()? - .get(target_reference)? - .as_object() - } - - /// Returns the attributes of a given target. - #[must_use] - pub fn get_attributes_for_target_reference_mut( - &mut self, - target_reference: &str, - ) -> Option<&mut PBXHashMap> { - self.target_attribute_references - .as_mut()? - .get_mut(target_reference)? - .as_object_mut() - } - - /// Git build configuration list - pub fn build_configuration_list(&self) -> Option>> { - self.objects - .upgrade()? - .borrow() - .get(&self.build_configuration_list_reference)? - .as_xc_configuration_list()? - .clone() - .pipe(Some) - } - - /// Get project projects - pub fn projects(&self) -> Option>> { - self.project_references - .as_ref()? - .iter() - .map(|v| { - v.iter() - .map(|(k, v)| Some((k, self.objects.upgrade()?.borrow().get(v)?.clone()))) - .flatten() - .collect::>() - }) - .collect::>() - .pipe(Some) - } - - /// Get Project main group. - pub fn main_group(&self) -> Rc> { - self.objects - .upgrade() - .expect("objects weak is invalid") - .borrow() - .get(&self.main_group_reference) - .expect("PBXProject should contain mainGroup") - .as_pbxfs_reference() - .expect("given reference point to PBXGroup") - .clone() - } - - /// Products Group - pub fn products_group(&self) -> Option>> { - let products_group = self.products_group_reference.as_ref()?; - let fs_reference = self - .objects - .upgrade()? - .borrow() - .get(products_group)? - .as_pbxfs_reference()? - .clone(); - - if fs_reference.borrow().is_group() { - Some(fs_reference) - } else { - None - } - } - - /// Get attributes for a given target reference - pub fn get_target_attributes(&mut self, target_reference: &str) -> Option<&PBXHashMap> { - let target_attributes = self.target_attribute_references.as_mut()?; - target_attributes.get(target_reference)?.as_object() - } - - /// Sets the attributes for the given target. - pub fn set_target_attributes(&mut self, attributes: PBXHashMap, target_reference: &str) { - let target_attributes = self - .target_attribute_references - .get_or_insert(Default::default()); - target_attributes.insert(target_reference.into(), attributes.into()); - } - - /// Remove attributes for a given target reference - pub fn remove_target_attributes(&mut self, target_reference: &str) -> Option { - if let Some(target_attributes) = self.target_attribute_references.as_mut() { - target_attributes - .remove(target_reference)? - .into_object() - .ok() - } else { - None - } - } - - /// Removes the all the target attributes - pub fn clear_all_target_attributes(&mut self) { - if let Some(target_attributes) = self.target_attribute_references.as_mut() { - target_attributes.clear(); - } - } - - /// Adds a remote swift package - pub fn add_swift_package( - &mut self, - repository_url: String, - product_name: String, - version_requirement: XCVersionRequirement, - target_name: String, - ) -> Result>> { - let objects = self - .objects - .upgrade() - .ok_or_else(|| anyhow::anyhow!("PBXObjectCollection is released"))?; - let mut objects = objects.borrow_mut(); - - // Get target reference for given target name - let (_, target) = match objects.get_target_by_name(&target_name) { - Some((reference, value)) => (reference, value), - None => bail!("No target found with {target_name:?}"), - }; - - // Add swift package reference - let (package_reference, package) = self.add_swift_package_reference( - &mut objects, - &product_name, - version_requirement, - repository_url, - )?; - - let mut target = target.borrow_mut(); - - // Add swift package product dependency - let (product_reference, _) = self.add_swift_package_product( - &mut target, - &mut objects, - product_name, - package_reference, - )?; - - // Build file - let build_file = - PBXBuildFile::new_from_swift_product(product_reference, self.objects.clone()) - .pipe(|v| Rc::new(RefCell::new(v))); - - let build_file_reference = objects.push(build_file.clone()); - - // Link the product - let (_, frameworks_build_phase) = objects - .get_build_phases_from_reference(&target.build_phase_references) - .into_iter() - .find(|(_, b)| b.borrow().is_frameworks()) - .ok_or_else(|| anyhow::anyhow!("frameworks Build Phase Not Found for {target_name}"))?; - - frameworks_build_phase - .borrow_mut() - .add_file_reference(build_file_reference); - - Ok(package) - } - - fn add_swift_package_product( - &mut self, - target: &mut RefMut, - objects: &mut RefMut, - product_name: String, - package_reference: String, - ) -> Result<(String, Rc>)> { - let product_details = if let Some(product) = - objects.get_product_dependency_from_target_reference(&package_reference) - { - product - } else { - let value = XCSwiftPackageProductDependency::new( - product_name, - package_reference.into(), - self.objects.clone(), - ) - .pipe(|v| Rc::new(RefCell::new(v))); - let reference = objects.push(value.clone()); - (reference, value) - }; - - target.insert_package_product_dependency(product_details.0.clone()); - - Ok(product_details) - } - - /// Create swift package reference if it doesn't already exists, update version_requirement and - /// return. This function would error if version_requirement is identical to current existing - /// one. - fn add_swift_package_reference( - &mut self, - objects: &mut RefMut, - product_name: &str, - version_requirement: XCVersionRequirement, - repository_url: String, - ) -> Result<(String, Rc>)> { - // self's swift package references - let package_references = self.package_references.get_or_insert(Default::default()); - - // Try finding swift package where repository_url == given repository_url. - let package_details = objects - .get_swift_package_reference_from_references(&package_references) - .into_iter() - .find(|(_, v)| { - if let Some(url) = &v.borrow().repository_url { - repository_url.eq(url) - } else { - false - } - }); - - match package_details { - // Package reference with given url already exists - Some((reference, value)) => { - let package = value.clone(); - let mut package = package.borrow_mut(); - let identical_version_requirement = package - .version_requirement - .as_ref() - .map(|v| version_requirement.eq(v)) - .unwrap_or_default(); - - if identical_version_requirement { - bail!("{product_name:?} with {version_requirement:?} is added already, reference key: {reference:?}") - } else { - package.set_version_requirement(version_requirement.into()); - Ok((reference, value)) - } - } - // Package reference with given url is new - None => { - let value = XCRemoteSwiftPackageReference::new( - repository_url.into(), - version_requirement.into(), - self.objects.clone(), - ) - .pipe(|v| Rc::new(RefCell::new(v))); - - let reference = objects.push(value.clone()); - - // Insert package reference - package_references.push(reference.clone()); - - Ok((reference, value)) - } - } - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashSet; - - use super::*; - fn get_project(demo_name: &str) -> (PBXRootObject, Rc>) { - use crate::pbxproj::pest::PBXProjectParser; - let root = env!("CARGO_MANIFEST_DIR"); - let file = PBXProjectParser::try_parse_from_file(format!( - "{root}/tests/samples/{demo_name}.pbxproj" - )) - .unwrap(); - let objects = PBXRootObject::try_from(file).unwrap(); - let inner = objects.objects(); - let project = inner - .iter() - .find(|s| s.1.is_pbx_project()) - .map(|(_, o)| o.as_pbx_project().unwrap().clone()) - .unwrap(); - drop(inner); - // root must live to next scope - (objects, project) - } - - #[test] - fn get_packages() { - let (_objects, project) = get_project("demo1"); - let project = project.borrow(); - let mut packages = project.packages().unwrap(); - let package = packages.remove(0).1; - let package = package.borrow(); - assert_eq!( - package.repository_url, - Some("https://github.com/apple/swift-log.git".into()), - ) - } - - #[test] - fn get_targets() { - let (_objects, project) = get_project("demo2"); - let project = project.borrow(); - let mut targets = project.targets(); - let target = targets.remove(0).1; - let target = target.borrow(); - assert_eq!( - target.name.as_ref().unwrap(), - "backbase-showcase-mobile-ios" - ) - } - - #[test] - fn get_build_configuration_list() { - let (_objects, project) = get_project("demo3"); - let project = project.borrow(); - let build_configuration_list = project.build_configuration_list().unwrap(); - let build_configuration_list = build_configuration_list.borrow(); - assert_eq!( - build_configuration_list.default_configuration_name, - Some("Release".into()) - ) - } - - #[test] - fn get_main_group() { - let (_objects, project) = get_project("demo4"); - let project = project.borrow(); - let main_group = project.main_group(); - let main_group = main_group.borrow(); - assert_eq!( - main_group.children_references(), - &HashSet::from([ - "4FE00ECD1A97227F00D83062".to_string(), - "4FF5E5001AA60ED3003996B4".to_string(), - "4FE00EC01A97227F00D83062".to_string(), - "4F303ED81A978C6100A83368".to_string(), - "4FE00EBF1A97227F00D83062".to_string() - ]) - ); - println!("{main_group:#?}") - } - - #[test] - fn get_products_group() { - let (_objects, project) = get_project("demo7"); - let project = project.borrow(); - let main_group = project.products_group().unwrap(); - let main_group = main_group.borrow(); - assert_eq!( - main_group.children_references(), - &HashSet::from(["53C4ED31159E740C0019285D".to_string()]) - ); - println!("{main_group:#?}") - } - - #[test] - fn add_swift_package_duplication() { - let (_objects, project) = get_project("demo1"); - let mut project = project.borrow_mut(); - let err = project - .add_swift_package( - "https://github.com/apple/swift-log.git".into(), - "Logging".into(), - XCVersionRequirement::Exact("1.4.2".into()), - "Wordle".into(), - ) - .unwrap_err(); - assert!(err.to_string().contains("added already")); - } - - #[test] - fn add_swift_package_with_new_version() { - let (root, project) = get_project("demo1"); - let mut project = project.borrow_mut(); - let new_package = project - .add_swift_package( - "https://github.com/apple/swift-log.git".into(), - "Logging".into(), - XCVersionRequirement::Exact("1.4.3".into()), - "Wordle".into(), - ) - .unwrap(); - assert_eq!( - new_package, - project.packages().unwrap().first().unwrap().1, - "new package should be added to project" - ); - println!("{:#?}", root.objects().build_files()); - assert_eq!( - new_package, - root.objects().swift_package_references().first().unwrap().1, - "new package should be added in object collection" - ); - } - - #[test] - fn add_swift_package_new_package() { - let (root, project) = get_project("demo1"); - let mut project = project.borrow_mut(); - let new_package = project - .add_swift_package( - "url".into(), - "Log".into(), - XCVersionRequirement::Exact("1.4.3".into()), - "Wordle".into(), - ) - .unwrap(); - assert_eq!( - new_package, - project.packages().unwrap()[1].1, - "new package should be added to project" - ); - let has_new_package = root.objects().swift_package_references().iter().any(|v| { - v.1.borrow() - .repository_url - .eq(&new_package.borrow().repository_url) - }); - assert!( - has_new_package, - "new package should be added in object collection" - ); + target_reference: &String, + ) -> Option<&&PBXHashMap> { + self.target_attributes.get(target_reference) } } -impl PBXObjectExt for PBXProject { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> Result { - let project_roots = if let Some(roots) = value - .remove_vec("projectRoots") - .map(|v| v.try_into_vec_strings().ok()) - .flatten() - { - roots - } else if let Some(root) = value.remove_string("projectRoot") { - vec![root] - } else { - vec![] - }; - - Ok(Self { - name: value.remove_string("name"), - compatibility_version: value.try_remove_value("compatibilityVersion")?.try_into()?, - development_region: value - .try_remove_value("developmentRegion") - .map(|v| v.try_into().ok()) - .ok() - .flatten(), - has_scanned_for_encodings: value - .try_remove_value("hasScannedForEncodings")? - .try_into()?, - known_regions: value.try_remove_value("knownRegions")?.try_into()?, - project_dir_path: value.try_remove_value("projectDirPath")?.try_into()?, - project_roots, - attributes: value.try_remove_value("attributes")?.try_into()?, - target_attribute_references: value - .remove_value("TargetAttributes") - .map(|v| v.try_into().ok()) - .flatten(), - package_references: value - .remove_value("packageReferences") - .map(|v| v.try_into().ok()) - .flatten(), - build_configuration_list_reference: value - .try_remove_value("buildConfigurationList")? - .try_into()?, - target_references: value.try_remove_value("targets")?.try_into()?, - project_references: value.remove_vec("projectReferences").map(|v| { - v.0.into_iter() - .map(|v| v.try_into_object()) - .flatten() - .map(|v| { - v.0.into_iter() - .map(|(k, v)| anyhow::Ok((k, v.try_into_string()?))) - .flatten() - .collect() - }) - .collect::>() - }), - main_group_reference: value.try_remove_value("mainGroup")?.try_into()?, - products_group_reference: value - .remove_value("productRefGroup") - .map(|v| v.try_into().ok()) - .flatten(), - objects, - }) - } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } -} +// impl PBXProject { +// /// Adds a remote swift package +// pub fn add_swift_package( +// &mut self, +// repository_url: String, +// product_name: String, +// version_requirement: XCVersionRequirement, +// target_name: String, +// ) -> Result>> { +// let objects = self +// .objects +// .upgrade() +// .ok_or_else(|| anyhow::anyhow!("PBXObjectCollection is released"))?; +// let mut objects = objects.borrow_mut(); + +// // Get target reference for given target name +// let (_, target) = match objects.get_target_by_name(&target_name) { +// Some((reference, value)) => (reference, value), +// None => bail!("No target found with {target_name:?}"), +// }; + +// // Add swift package reference +// let (package_reference, package) = self.add_swift_package_reference( +// &mut objects, +// &product_name, +// version_requirement, +// repository_url, +// )?; + +// let mut target = target.borrow_mut(); + +// // Add swift package product dependency +// let (product_reference, _) = self.add_swift_package_product( +// &mut target, +// &mut objects, +// product_name, +// package_reference, +// )?; + +// // Build file +// let build_file = +// PBXBuildFile::new_from_swift_product(product_reference, self.objects.clone()) +// .pipe(|v| Rc::new(RefCell::new(v))); + +// let build_file_reference = objects.push(build_file.clone()); + +// // Link the product +// let (_, frameworks_build_phase) = objects +// .get_build_phases_from_reference(&target.build_phase_references) +// .into_iter() +// .find(|(_, b)| b.borrow().is_frameworks()) +// .ok_or_else(|| anyhow::anyhow!("frameworks Build Phase Not Found for {target_name}"))?; + +// frameworks_build_phase +// .borrow_mut() +// .add_file_reference(build_file_reference); + +// Ok(package) +// } + +// fn add_swift_package_product( +// &mut self, +// target: &mut RefMut, +// objects: &mut RefMut, +// product_name: String, +// package_reference: String, +// ) -> Result<(String, Rc>)> { +// let product_details = if let Some(product) = +// objects.get_product_dependency_from_target_reference(&package_reference) +// { +// product +// } else { +// let value = XCSwiftPackageProductDependency::new( +// product_name, +// package_reference.into(), +// self.objects.clone(), +// ) +// .pipe(|v| Rc::new(RefCell::new(v))); +// let reference = objects.push(value.clone()); +// (reference, value) +// }; + +// target.insert_package_product_dependency(product_details.0.clone()); + +// Ok(product_details) +// } + +// /// Create swift package reference if it doesn't already exists, update version_requirement and +// /// return. This function would error if version_requirement is identical to current existing +// /// one. +// fn add_swift_package_reference( +// &mut self, +// objects: &mut RefMut, +// product_name: &str, +// version_requirement: XCVersionRequirement, +// repository_url: String, +// ) -> Result<(String, Rc>)> { +// // self's swift package references +// let package_references = self.package_references.get_or_insert(Default::default()); + +// // Try finding swift package where repository_url == given repository_url. +// let package_details = objects +// .get_swift_package_reference_from_references(&package_references) +// .into_iter() +// .find(|(_, v)| { +// if let Some(url) = &v.borrow().repository_url { +// repository_url.eq(url) +// } else { +// false +// } +// }); + +// match package_details { +// // Package reference with given url already exists +// Some((reference, value)) => { +// let package = value.clone(); +// let mut package = package.borrow_mut(); +// let identical_version_requirement = package +// .version_requirement +// .as_ref() +// .map(|v| version_requirement.eq(v)) +// .unwrap_or_default(); + +// if identical_version_requirement { +// bail!("{product_name:?} with {version_requirement:?} is added already, reference key: {reference:?}") +// } else { +// package.set_version_requirement(version_requirement.into()); +// Ok((reference, value)) +// } +// } +// // Package reference with given url is new +// None => { +// let value = XCRemoteSwiftPackageReference::new( +// repository_url.into(), +// version_requirement.into(), +// self.objects.clone(), +// ) +// .pipe(|v| Rc::new(RefCell::new(v))); + +// let reference = objects.push(value.clone()); + +// // Insert package reference +// package_references.push(reference.clone()); + +// Ok((reference, value)) +// } +// } +// } +// } + +// #[cfg(test)] +// mod tests { +// use std::collections::HashSet; + +// use super::*; +// fn get_project(demo_name: &str) -> (PBXRootObject, Rc>) { +// use crate::pbxproj::pest::PBXProjectParser; +// let root = env!("CARGO_MANIFEST_DIR"); +// let file = PBXProjectParser::try_parse_from_file(format!( +// "{root}/tests/samples/{demo_name}.pbxproj" +// )) +// .unwrap(); +// let objects = PBXRootObject::try_from(file).unwrap(); +// let inner = objects.objects(); +// let project = inner +// .iter() +// .find(|s| s.1.is_pbx_project()) +// .map(|(_, o)| o.as_pbx_project().unwrap().clone()) +// .unwrap(); +// drop(inner); +// // root must live to next scope +// (objects, project) +// } + +// #[test] +// fn get_packages() { +// let (_objects, project) = get_project("demo1"); +// let project = project.borrow(); +// let mut packages = project.packages().unwrap(); +// let package = packages.remove(0).1; +// let package = package.borrow(); +// assert_eq!( +// package.repository_url, +// Some("https://github.com/apple/swift-log.git".into()), +// ) +// } + +// #[test] +// fn get_targets() { +// let (_objects, project) = get_project("demo2"); +// let project = project.borrow(); +// let mut targets = project.targets(); +// let target = targets.remove(0).1; +// let target = target.borrow(); +// assert_eq!( +// target.name.as_ref().unwrap(), +// "backbase-showcase-mobile-ios" +// ) +// } + +// #[test] +// fn get_build_configuration_list() { +// let (_objects, project) = get_project("demo3"); +// let project = project.borrow(); +// let build_configuration_list = project.build_configuration_list().unwrap(); +// let build_configuration_list = build_configuration_list.borrow(); +// assert_eq!( +// build_configuration_list.default_configuration_name, +// Some("Release".into()) +// ) +// } + +// #[test] +// fn get_main_group() { +// let (_objects, project) = get_project("demo4"); +// let project = project.borrow(); +// let main_group = project.main_group(); +// let main_group = main_group.borrow(); +// assert_eq!( +// main_group.children_references(), +// &HashSet::from([ +// "4FE00ECD1A97227F00D83062".to_string(), +// "4FF5E5001AA60ED3003996B4".to_string(), +// "4FE00EC01A97227F00D83062".to_string(), +// "4F303ED81A978C6100A83368".to_string(), +// "4FE00EBF1A97227F00D83062".to_string() +// ]) +// ); +// println!("{main_group:#?}") +// } + +// #[test] +// fn get_products_group() { +// let (_objects, project) = get_project("demo7"); +// let project = project.borrow(); +// let main_group = project.products_group().unwrap(); +// let main_group = main_group.borrow(); +// assert_eq!( +// main_group.children_references(), +// &HashSet::from(["53C4ED31159E740C0019285D".to_string()]) +// ); +// println!("{main_group:#?}") +// } + +// #[test] +// fn add_swift_package_duplication() { +// let (_objects, project) = get_project("demo1"); +// let mut project = project.borrow_mut(); +// let err = project +// .add_swift_package( +// "https://github.com/apple/swift-log.git".into(), +// "Logging".into(), +// XCVersionRequirement::Exact("1.4.2".into()), +// "Wordle".into(), +// ) +// .unwrap_err(); +// assert!(err.to_string().contains("added already")); +// } + +// #[test] +// fn add_swift_package_with_new_version() { +// let (root, project) = get_project("demo1"); +// let mut project = project.borrow_mut(); +// let new_package = project +// .add_swift_package( +// "https://github.com/apple/swift-log.git".into(), +// "Logging".into(), +// XCVersionRequirement::Exact("1.4.3".into()), +// "Wordle".into(), +// ) +// .unwrap(); +// assert_eq!( +// new_package, +// project.packages().unwrap().first().unwrap().1, +// "new package should be added to project" +// ); +// println!("{:#?}", root.objects().build_files()); +// assert_eq!( +// new_package, +// root.objects().swift_package_references().first().unwrap().1, +// "new package should be added in object collection" +// ); +// } + +// #[test] +// fn add_swift_package_new_package() { +// let (root, project) = get_project("demo1"); +// let mut project = project.borrow_mut(); +// let new_package = project +// .add_swift_package( +// "url".into(), +// "Log".into(), +// XCVersionRequirement::Exact("1.4.3".into()), +// "Wordle".into(), +// ) +// .unwrap(); +// assert_eq!( +// new_package, +// project.packages().unwrap()[1].1, +// "new package should be added to project" +// ); +// let has_new_package = root.objects().swift_package_references().iter().any(|v| { +// v.1.borrow() +// .repository_url +// .eq(&new_package.borrow().repository_url) +// }); +// assert!( +// has_new_package, +// "new package should be added in object collection" +// ); +// } +// } diff --git a/src/pbxproj/object/swift_package/dependency.rs b/src/pbxproj/object/swift_package/dependency.rs index bb24b2e..40644d8 100644 --- a/src/pbxproj/object/swift_package/dependency.rs +++ b/src/pbxproj/object/swift_package/dependency.rs @@ -1,52 +1,31 @@ use crate::pbxproj::*; -use std::{cell::RefCell, rc::Rc}; /// [`PBXObject`] represents swift package dependency /// /// [`PBXObject`]: crate::pbxproj::PBXObject #[derive(Debug, derive_new::new)] -pub struct XCSwiftPackageProductDependency { +pub struct XCSwiftPackageProductDependency<'a> { + /// ID Reference + pub id: String, /// Product name. - pub product_name: String, + pub product_name: &'a String, /// Package reference. - package_reference: Option, - objects: WeakPBXObjectCollection, + pub package: Option>, } -impl XCSwiftPackageProductDependency { - /// Package the product dependency refers to. - pub fn package(&self) -> Option>> { - self.objects - .upgrade()? - .borrow() - .get_swift_package_reference(self.package_reference()?) - } - - /// Get a reference to the xcswift package product dependency's package reference. - #[must_use] - pub fn package_reference(&self) -> Option<&String> { - self.package_reference.as_ref() - } - - /// Set the xcswift package product dependency's package reference. - pub fn set_package_reference(&mut self, package_reference: Option) -> Option { - std::mem::replace(&mut self.package_reference, package_reference) - } -} - -impl PBXObjectExt for XCSwiftPackageProductDependency { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result +impl<'a> AsPBXObject<'a> for XCSwiftPackageProductDependency<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> anyhow::Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - product_name: value.try_remove_string("productName")?, - package_reference: value.remove_string("package"), - objects, + id, + product_name: value.try_get_string("productName")?, + package: value.get_string("package").and_then(|key| objects.get(key)), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/swift_package/remote.rs b/src/pbxproj/object/swift_package/remote.rs index 030acb8..1fe64b9 100644 --- a/src/pbxproj/object/swift_package/remote.rs +++ b/src/pbxproj/object/swift_package/remote.rs @@ -5,24 +5,25 @@ use crate::pbxproj::*; /// [`PBXObject`]: crate::pbxproj::PBXObject /// [`XCSwiftPackageProductDependency`]: crate::pbxproj::XCSwiftPackageProductDependency #[derive(Debug, derive_new::new)] -pub struct XCRemoteSwiftPackageReference { +pub struct XCRemoteSwiftPackageReference<'a> { + /// ID Reference + pub id: String, /// Repository url. - pub repository_url: Option, + pub repository_url: Option<&'a String>, /// Version rules. pub version_requirement: Option, - objects: WeakPBXObjectCollection, } -impl Eq for XCRemoteSwiftPackageReference {} +impl<'a> Eq for XCRemoteSwiftPackageReference<'a> {} -impl PartialEq for XCRemoteSwiftPackageReference { +impl<'a> PartialEq for XCRemoteSwiftPackageReference<'a> { fn eq(&self, other: &Self) -> bool { self.repository_url == other.repository_url && self.version_requirement == other.version_requirement } } -impl XCRemoteSwiftPackageReference { +impl<'a> XCRemoteSwiftPackageReference<'a> { /// It returns the name of the package reference. pub fn name(&self) -> Option<&str> { self.repository_url @@ -43,25 +44,22 @@ impl XCRemoteSwiftPackageReference { } } -impl PBXObjectExt for XCRemoteSwiftPackageReference { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result +impl<'a> AsPBXObject<'a> for XCRemoteSwiftPackageReference<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + _objects: &'a PBXObjectCollection, + ) -> anyhow::Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - repository_url: value - .remove_value("repositoryURL") - .map(|v| v.try_into().ok()) - .flatten(), + id, + repository_url: value.get_string("repositoryURL"), version_requirement: value - .remove_value("requirement") + .get_value("requirement") .map(|v| v.try_into().ok()) .flatten(), - objects, }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/swift_package/version.rs b/src/pbxproj/object/swift_package/version.rs index 36f52d4..8ac3461 100644 --- a/src/pbxproj/object/swift_package/version.rs +++ b/src/pbxproj/object/swift_package/version.rs @@ -22,28 +22,30 @@ pub enum XCVersionRequirement { Revision(String), } -impl TryFrom for XCVersionRequirement { +impl TryFrom<&PBXValue> for XCVersionRequirement { type Error = anyhow::Error; - fn try_from(value: PBXValue) -> Result { - let mut map = value.try_into_object()?; - let key = map.try_remove_string("kind")?; + fn try_from(value: &PBXValue) -> Result { + let map = value + .as_object() + .ok_or_else(|| anyhow::anyhow!("Can get XCVersionRequirement for non object type"))?; + let key = map.try_get_string("kind")?; match key.as_str() { - "bracnh" => Self::Branch(map.try_remove_string(&key)?), - "revision" => Self::Revision(map.try_remove_string(&key)?), - "exactVersion" => Self::Exact(map.try_remove_string("version")?), + "bracnh" => Self::Branch(map.try_get_string(&key)?.to_string()), + "revision" => Self::Revision(map.try_get_string(&key)?.to_string()), + "exactVersion" => Self::Exact(map.try_get_string("version")?.to_string()), "versionRange" => { - let min = map.try_remove_string("minimumVersion")?; - let max = map.try_remove_string("maximumVersion")?; - Self::Range(min, max) + let min = map.try_get_string("minimumVersion")?; + let max = map.try_get_string("maximumVersion")?; + Self::Range(min.to_string(), max.to_string()) } "upToNextMinorVersion" => { - let min = map.try_remove_string("minimumVersion")?; - Self::UpToNextMinorVersion(min) + let min = map.try_get_string("minimumVersion")?; + Self::UpToNextMinorVersion(min.to_string()) } "upToNextMajorVersion" => { - let max = map.try_remove_string("maximumVersion")?; - Self::UpToNextMajorVersion(max) + let max = map.try_get_string("maximumVersion")?; + Self::UpToNextMajorVersion(max.to_string()) } k => bail!("Unkown kind {k}"), } diff --git a/src/pbxproj/object/target.rs b/src/pbxproj/object/target.rs index 8ea134c..8d2e2a6 100644 --- a/src/pbxproj/object/target.rs +++ b/src/pbxproj/object/target.rs @@ -1,7 +1,6 @@ mod dependency; pub use dependency::*; -use crate::pbxproj::WeakPBXObjectCollection; use anyhow::Result; use crate::pbxproj::*; @@ -10,139 +9,94 @@ use crate::pbxproj::*; /// /// variants: `PBXAggregateTarget`, `PBXLegacyTarget`, and `PBXNativeTarget` #[derive(Debug, derive_new::new)] -pub struct PBXTarget { +pub struct PBXTarget<'a> { + /// ID Reference + pub id: String, /// Target name. - pub name: Option, + pub name: Option<&'a String>, /// Target product name. - pub product_name: Option, + pub product_name: Option<&'a String>, /// Target product type. pub product_type: PBXProductType, /// Target build configuration list. - pub(crate) build_configuration_list_reference: Option, + pub build_configuration_list: Option>, /// Target build phase references. - pub(crate) build_phase_references: Vec, + pub build_phases: Vec>, /// Target build rule references. - pub(crate) build_rule_references: Vec, + pub build_rules: Vec>, /// Target dependency references. - pub(crate) target_dependency_references: Vec, + pub target_dependencies: Vec>, /// Target product reference. - pub(crate) product_reference: Option, + pub product: Option>, /// Swift package product references. - pub(crate) package_product_dependency_references: Vec, + pub package_product_dependencies: Vec>, /// Target Kind - pub kind: PBXTargetKind, + pub kind: &'a PBXTargetKind, /// Target product install path. (relevant only for `PBXNativeTarget`) - pub product_install_path: Option, + pub product_install_path: Option<&'a String>, /// Path to the build tool that is invoked (required) (relevant only for `PBXLegeacyTarget`) - pub build_tool_path: Option, + pub build_tool_path: Option<&'a String>, /// Build arguments to be passed to the build tool. (relevant only for `PBXLegeacyTarget`) - pub build_arguments_string: Option, + pub build_arguments_string: Option<&'a String>, /// Whether or not to pass Xcode build settings as environment variables down to the tool when invoked (relevant only for `PBXLegeacyTarget`) pub pass_build_settings_in_environment: Option, /// The directory where the build tool will be invoked during a build - pub build_working_directory: Option, - objects: WeakPBXObjectCollection, + pub build_working_directory: Option<&'a String>, } -impl PBXTarget { - /// Get build configuration list from data for current target - pub fn get_build_configuration_list(&self, _data: &PBXRootObject) -> () {} - - /// Set build configuration list reference - pub fn set_build_configuration_list(&mut self, reference: Option) { - self.build_configuration_list_reference = reference; - } - - /// Set the target's build phase references. - pub fn set_build_phases(&mut self, references: Vec) { - self.build_phase_references = references; - } - - /// Set the target's build rule references. - pub fn set_build_rule_references(&mut self, build_rule_references: Vec) { - self.build_rule_references = build_rule_references; - } - - /// Get target dependences from data for current target - pub fn get_target_dependences(&self, _data: &PBXRootObject) -> () {} - - /// Set the target's dependency references. - pub fn set_target_dependency_references(&mut self, target_dependency_references: Vec) { - self.target_dependency_references = target_dependency_references; - } - - /// Set the target's dependency references. - pub fn insert_package_product_dependency(&mut self, reference: String) { - self.target_dependency_references.push(reference) - } - - /// Set the target's package product dependency references. - pub fn set_package_product_dependency_references( - &mut self, - package_product_dependency_references: Vec, - ) { - self.package_product_dependency_references = package_product_dependency_references; - } - - /// Set the target's product reference. - pub fn set_product_reference(&mut self, product_reference: Option) { - self.product_reference = product_reference; - } -} - -impl PBXObjectExt for PBXTarget { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> Result +impl<'a> AsPBXObject<'a> for PBXTarget<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> Result where - Self: Sized, + Self: Sized + 'a, { let kind = value - .try_remove_kind("isa")? - .try_into_target_kind() + .get_kind("isa") + .and_then(|v| v.as_pbx_target()) .unwrap(); Ok(Self { - name: value.remove_string("name"), - product_name: value.remove_string("productName"), - product_type: value - .try_remove_string("productType") - .unwrap_or_default() - .into(), - build_configuration_list_reference: value.remove_string("buildConfigurationList"), - build_phase_references: value - .try_remove_vec("buildPhases")? - .try_into_vec_strings()?, - build_rule_references: value - .remove_vec("buildRules") - .map(|v| v.try_into_vec_strings().ok()) - .flatten() + id, + name: value.get_string("name"), + product_name: value.get_string("productName"), + product_type: value.try_get_string("productType").unwrap().as_str().into(), + build_configuration_list: value + .get_string("buildConfigurationList") + .and_then(|key| objects.get(key)), + build_phases: value + .get_vec("buildPhases") + .map(|vec| objects.get_vec(vec.as_vec_strings())) .unwrap_or_default(), - target_dependency_references: value - .try_remove_vec("dependencies")? - .try_into_vec_strings()?, - product_reference: value.remove_string("productReference"), - package_product_dependency_references: value - .remove_vec("packageProductDependencies") - .map(|v| v.try_into_vec_strings().ok()) - .flatten() + build_rules: value + .get_vec("buildRules") + .map(|v| objects.get_vec(v.as_vec_strings())) + .unwrap_or_default(), + target_dependencies: value + .get_vec("dependencies") + .map(|v| objects.get_vec(v.as_vec_strings())) + .unwrap_or_default(), + product: value + .get_string("productReference") + .and_then(|key| objects.get(key)), + package_product_dependencies: value + .get_vec("packageProductDependencies") + .map(|v| objects.get_vec(v.as_vec_strings())) .unwrap_or_default(), - objects, product_install_path: if kind.is_native() { - value.remove_string("productInstallPath") + value.get_string("productInstallPath") } else { None }, kind, - - build_tool_path: value.remove_string("buildToolPath"), - build_arguments_string: value.remove_string("buildArgumentsString"), + build_tool_path: value.get_string("buildToolPath"), + build_arguments_string: value.get_string("buildArgumentsString"), pass_build_settings_in_environment: value - .remove_number("passBuildSettingsInEnvironment") - .map(|n| n == 1), - build_working_directory: value.remove_string("buildWorkingDirectory"), + .get_number("passBuildSettingsInEnvironment") + .map(|n| n == &1), + build_working_directory: value.get_string("buildWorkingDirectory"), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/object/target/dependency.rs b/src/pbxproj/object/target/dependency.rs index 4a2a0ff..c97c0a0 100644 --- a/src/pbxproj/object/target/dependency.rs +++ b/src/pbxproj/object/target/dependency.rs @@ -2,70 +2,41 @@ use crate::pbxproj::*; /// [`PBXObject`] referencing other targets through content proxies. #[derive(Debug, derive_new::new)] -pub struct PBXTargetDependency { +pub struct PBXTargetDependency<'a> { + /// ID Reference + pub id: String, /// Target name. - pub name: Option, + pub name: Option<&'a String>, /// Platform filter attribute. - /// Introduced in: Xcode 11 - pub platform_filter: Option, - /// Target reference. - target_reference: Option, - /// Target proxy reference. - target_proxy_reference: Option, + pub platform_filter: Option<&'a String>, + /// Target + pub target: Option>, + /// Target proxy + pub target_proxy: Option>, /// Product reference. - product_reference: Option, - objects: WeakPBXObjectCollection, + pub product: Option>, } -impl PBXTargetDependency { - /// Target. - pub fn target(&self, _data: &PBXRootObject) -> Option { - // targetReference?.getObject() - todo!() - } - - /// Target. - pub fn set_target(&mut self) -> Option { - todo!() - } - /// Target proxy. - pub fn target_proxy(&self) -> Option { - // targetProxyReference?.getObject() - todo!() - } - /// Target proxy. - pub fn set_target_proxy(&mut self) -> Option { - todo!() - } - - /// Product. - pub fn set_product(&mut self) -> Option { - todo!() - } - - /// Product. - pub fn product(&self) -> Option { - // productReference?.getObject() - todo!() - } -} - -impl PBXObjectExt for PBXTargetDependency { - fn from_hashmap(mut value: PBXHashMap, objects: WeakPBXObjectCollection) -> anyhow::Result +impl<'a> AsPBXObject<'a> for PBXTargetDependency<'a> { + fn as_pbx_object( + id: String, + value: &'a PBXHashMap, + objects: &'a PBXObjectCollection, + ) -> anyhow::Result where - Self: Sized, + Self: Sized + 'a, { Ok(Self { - name: value.remove_string("name"), - platform_filter: value.remove_string("platformFilter"), - target_reference: value.remove_string("target"), - target_proxy_reference: value.remove_string("targetProxy"), - product_reference: value.remove_string("productRef"), - objects, + id, + name: value.get_string("name"), + platform_filter: value.get_string("platformFilter"), + target: value.get_string("target").and_then(|key| objects.get(key)), + target_proxy: value + .get_string("targetProxy") + .and_then(|key| objects.get(key)), + product: value + .get_string("productRef") + .and_then(|key| objects.get(key)), }) } - - fn to_hashmap(&self) -> PBXHashMap { - todo!() - } } diff --git a/src/pbxproj/rep.rs b/src/pbxproj/rep.rs index a94a276..139597f 100644 --- a/src/pbxproj/rep.rs +++ b/src/pbxproj/rep.rs @@ -1,201 +1,2 @@ -use super::{PBXFSReference, PBXHashMap, PBXObject, PBXObjectCollection, PBXProject, PBXTarget}; -use anyhow::Result; -use std::{ - cell::{Ref, RefCell, RefMut}, - collections::HashMap, - path::{Path, PathBuf}, - rc::{Rc, Weak}, -}; -use tap::Pipe; -/// `Main` Representation of project.pbxproj file -#[derive(Debug, derive_new::new)] -pub struct PBXRootObject { - /// archiveVersion - archive_version: u8, - /// objectVersion - object_version: u8, - /// classes - classes: PBXHashMap, - /// Objects - objects: Rc>, - /// rootObjectReference - root_object_reference: String, -} -impl PBXRootObject { - /// Get the pbxproject's archive version. - #[must_use] - pub fn archive_version(&self) -> u8 { - self.archive_version - } - - /// Get the pbxproject's object version. - #[must_use] - pub fn object_version(&self) -> u8 { - self.object_version - } - - /// Get a reference to the pbxproject's classes. - #[must_use] - pub fn classes(&self) -> &PBXHashMap { - &self.classes - } - - /// Get a reference to the pbxproject's root object reference. - #[must_use] - pub fn root_object_reference(&self) -> &str { - self.root_object_reference.as_ref() - } - - /// Get a reference to the pbxroot object's objects. - #[must_use] - pub fn objects<'a>(&'a self) -> Ref<'a, PBXObjectCollection> { - self.objects.borrow() - } - - /// Get a reference to the pbxroot object's objects. - #[must_use] - pub fn objects_mut<'a>(&'a self) -> RefMut<'a, PBXObjectCollection> { - self.objects.borrow_mut() - } - - /// Get a weak reference to objects. - #[must_use] - pub fn clone_objects<'a>(&'a self) -> Weak> { - Rc::downgrade(&self.objects) - } - - /// Get Project's targets - pub fn targets(&self) -> Vec>> { - self.objects() - .targets() - .into_iter() - .map(|(_, o)| o) - .collect() - } - - /// Get Root PBXProject - pub fn root_project(&self) -> Option>> { - self.objects() - .projects() - .into_iter() - .find(|(k, _)| k == self.root_object_reference()) - .map(|(_, o)| o) - } - - /// Get root group - pub fn root_group(&self) -> Option>> { - Some(self.root_project()?.borrow().main_group()) - } -} - -impl TryFrom for PBXRootObject { - type Error = anyhow::Error; - fn try_from(mut map: PBXHashMap) -> Result { - let archive_version = map.try_remove_number("archiveVersion")? as u8; - let object_version = map.try_remove_number("objectVersion")? as u8; - let classes = map.try_remove_object("classes").unwrap_or_default(); - let root_object_reference = map.try_remove_string("rootObject")?; - let refcell = Rc::new(RefCell::new(PBXObjectCollection::default())); - let objects = map - .try_remove_object("objects")? - .0 - .into_iter() - .map(|(k, v)| anyhow::Ok((k, PBXObject::new(v, Rc::downgrade(&refcell))?))) - .flatten() - .collect::>(); - - refcell.borrow_mut().set_inner(objects); - refcell - .borrow() - .groups() - .into_iter() - .for_each(|(_, group)| { - let fs_reference = group.borrow_mut(); - fs_reference.assign_parent_to_children(Rc::downgrade(&group)) - }); - - Ok(Self { - archive_version, - object_version, - classes, - objects: refcell, - root_object_reference, - }) - } -} - -impl TryFrom<&str> for PBXRootObject { - type Error = anyhow::Error; - fn try_from(content: &str) -> Result { - use crate::pbxproj::pest::PBXProjectParser; - - PBXProjectParser::try_from_str(content)?.pipe(Self::try_from) - } -} - -impl TryFrom for PBXRootObject { - type Error = anyhow::Error; - fn try_from(content: String) -> Result { - Self::try_from(content.as_str()) - } -} - -impl TryFrom<&Path> for PBXRootObject { - type Error = anyhow::Error; - - fn try_from(value: &Path) -> Result { - std::fs::read_to_string(&value) - .map_err(|e| anyhow::anyhow!("PBXProjectData from path {value:?}: {e}"))? - .pipe(TryFrom::try_from) - } -} - -impl TryFrom for PBXRootObject { - type Error = anyhow::Error; - - fn try_from(value: PathBuf) -> Result { - Self::try_from(value.as_path()) - } -} - -#[test] -#[ignore = "check_output"] -fn test_parse() { - let test_content = include_str!("../../tests/samples/demo1.pbxproj"); - let project = PBXRootObject::try_from(test_content).unwrap(); - - println!("{:#?}", project.root_project()); - println!("{:#?}", project.targets()); -} - -#[cfg(test)] -macro_rules! test_demo_file { - ($name:expr) => {{ - let (root, name) = (env!("CARGO_MANIFEST_DIR"), stringify!($name)); - let path = format!("{root}/tests/samples/{name}.pbxproj"); - let file = crate::pbxproj::PBXRootObject::try_from(std::path::PathBuf::from(path)); - if file.is_err() { - println!("Error: {:#?}", file.as_ref().unwrap_err()) - } - assert!(file.is_ok()); - file.unwrap() - }}; -} -#[cfg(test)] -pub(crate) use test_demo_file; - -#[cfg(test)] -mod create_tests { - macro_rules! test_samples { - ($($name:ident),*) => { - $(#[test] - fn $name() { - test_demo_file!($name); - })* - }; - } - - test_samples![demo1, demo2, demo3, demo4, demo5, demo6, demo7, demo8, demo9]; -} diff --git a/src/pbxproj/value.rs b/src/pbxproj/value.rs index 1042c35..e21652d 100644 --- a/src/pbxproj/value.rs +++ b/src/pbxproj/value.rs @@ -274,6 +274,16 @@ impl PBXVec { Ok(collector) } + pub(crate) fn as_vec_strings<'a>(&'a self) -> Vec<&'a String> { + let mut collector = vec![]; + for value in self.0.iter() { + if let Some(str) = value.as_string() { + collector.push(str); + } + } + collector + } + pub(crate) fn try_into_vec + From>(self) -> Result> { let mut collector = vec![]; for value in self.0 {