Skip to content

Commit

Permalink
ref: create PBXObject at request
Browse files Browse the repository at this point in the history
No more weak references. At first I thought Rc wrapped with Arc would be
thread safe, but unfortunately it isn't. Arc and mutex under old
implementation won't cut it as it block on immutable access.
  • Loading branch information
kkharji committed Jun 19, 2022
1 parent e3e7c4a commit 8c045ae
Show file tree
Hide file tree
Showing 30 changed files with 1,567 additions and 2,214 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
53 changes: 53 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<P: AsRef<Path>>(xcodeproj_folder: P) -> Result<Self> {
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()
}
}
200 changes: 198 additions & 2 deletions src/pbxproj/mod.rs
Original file line number Diff line number Diff line change
@@ -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<PBXHashMap> for PBXRootObject {
type Error = anyhow::Error;
fn try_from(mut map: PBXHashMap) -> Result<Self> {
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<Self> {
use crate::pbxproj::pest::PBXProjectParser;

PBXProjectParser::try_from_str(content)?.pipe(Self::try_from)
}
}

impl TryFrom<String> for PBXRootObject {
type Error = anyhow::Error;
fn try_from(content: String) -> Result<Self> {
Self::try_from(content.as_str())
}
}

impl TryFrom<&Path> for PBXRootObject {
type Error = anyhow::Error;

fn try_from(value: &Path) -> Result<Self> {
std::fs::read_to_string(&value)
.map_err(|e| anyhow::anyhow!("PBXProjectData from path {value:?}: {e}"))?
.pipe(TryFrom::try_from)
}
}

impl TryFrom<PathBuf> for PBXRootObject {
type Error = anyhow::Error;

fn try_from(value: PathBuf) -> Result<Self> {
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::<Vec<_>>()
);

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];
}
56 changes: 21 additions & 35 deletions src/pbxproj/object/build/config.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
objects: Weak<RefCell<PBXObjectCollection>>,
pub build_settings: &'a PBXHashMap,
/// Base xcconfig file.
pub base_configuration: Option<PBXFSReference<'a>>,
}

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<String>) -> Option<String> {
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<RefCell<PBXObjectCollection>>,
impl<'a> AsPBXObject<'a> for XCBuildConfiguration<'a> {
fn as_pbx_object(
id: String,
value: &'a PBXHashMap,
objects: &'a PBXObjectCollection,
) -> anyhow::Result<Self>
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!()
}
}
Loading

0 comments on commit 8c045ae

Please sign in to comment.