Skip to content

Commit 8c045ae

Browse files
committed
ref: create PBXObject at request
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.
1 parent e3e7c4a commit 8c045ae

30 files changed

+1567
-2214
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "xcodeproj"
3-
version = "0.2.3"
3+
version = "0.2.4"
44
edition = "2021"
55
description = "xcodeproj reader and parser."
66
license = "MIT OR Apache-2.0"

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# XcodeProj
22

3-
Work-in-progress XcodeProj reader and writer.
3+
XcodeProj reader and writer.
44

5-
Currently optimized for reading and not garanted to be used to modify existing xcodeproj.
5+
Currently optimized for reading. please see docs for usage.
66

77
## Milestones
88

99
- [x] parse `*.xcodeproj` through [pest]
1010
- [x] parse [pest] ast to `PBXRootObject`, as an meaningful abstraction.
11-
- [ ] add helper methods to maniuplate and read pbxproj objects.
12-
- [ ] write `ProjectData` back to `*.xcodeproj` filetype.
11+
- [ ] add helper methods to manipulate and read pbxproj objects.
12+
- [ ] write to `*.xcodeproj` filetype.
1313
- [ ] preserve comments and reduce git conflicts.
1414
- [ ] support reading XCWorkspace and XCScheme
1515

src/lib.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,59 @@
55
#![deny(rustdoc::broken_intra_doc_links)]
66
#![doc = include_str!("../README.md")]
77

8+
use anyhow::Result;
9+
use pbxproj::{PBXFSReference, PBXObjectCollection, PBXProject, PBXRootObject};
10+
use std::path::{Path, PathBuf};
11+
812
mod macros;
913
pub mod pbxproj;
1014
pub mod xcode;
15+
16+
/// Main presentation of XCodeProject
17+
pub struct XCodeProject {
18+
root: PathBuf,
19+
pbxproj: PBXRootObject,
20+
}
21+
22+
impl XCodeProject {
23+
/// Create new XCodeProject object
24+
pub fn new<P: AsRef<Path>>(xcodeproj_folder: P) -> Result<Self> {
25+
let xcodeproj_folder = xcodeproj_folder.as_ref();
26+
let pbxproj_path = xcodeproj_folder.join("project.pbxproj");
27+
28+
Ok(Self {
29+
root: xcodeproj_folder.parent().unwrap().to_path_buf(),
30+
pbxproj: pbxproj_path.try_into()?,
31+
})
32+
}
33+
34+
/// Get archive version
35+
pub fn archive_version(&self) -> u8 {
36+
self.pbxproj.archive_version()
37+
}
38+
39+
/// Get pbxproj object version
40+
pub fn object_version(&self) -> u8 {
41+
self.pbxproj.object_version()
42+
}
43+
44+
/// Get root project of pbxproj
45+
pub fn root_project(&self) -> PBXProject {
46+
self.pbxproj.root_project()
47+
}
48+
49+
/// Get root group of pbxproj
50+
pub fn root_group(&self) -> PBXFSReference {
51+
self.pbxproj.root_group()
52+
}
53+
54+
/// Get pbxproj objects
55+
pub fn objects(&self) -> &PBXObjectCollection {
56+
self.pbxproj.objects()
57+
}
58+
59+
/// Get mutable reference of pbxproj objects
60+
pub fn objects_mut(&mut self) -> &mut PBXObjectCollection {
61+
self.pbxproj.objects_mut()
62+
}
63+
}

src/pbxproj/mod.rs

Lines changed: 198 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,205 @@
11
//! pbxproj file serialize and deserializer
22
mod object;
3-
mod rep;
43
mod value;
54

65
pub(crate) mod pest;
76
pub use object::*;
8-
pub use rep::*;
97
pub use value::*;
8+
9+
use anyhow::Result;
10+
use std::path::{Path, PathBuf};
11+
use tap::Pipe;
12+
13+
/// `Main` Representation of project.pbxproj file
14+
#[derive(Debug, derive_new::new, derive_deref_rs::Deref)]
15+
pub struct PBXRootObject {
16+
/// archiveVersion
17+
archive_version: u8,
18+
/// objectVersion
19+
object_version: u8,
20+
/// classes
21+
classes: PBXHashMap,
22+
/// Objects
23+
#[deref]
24+
objects: PBXObjectCollection,
25+
/// rootObjectReference
26+
root_object_reference: String,
27+
}
28+
29+
impl PBXRootObject {
30+
/// Get the pbxproject's archive version.
31+
#[must_use]
32+
pub fn archive_version(&self) -> u8 {
33+
self.archive_version
34+
}
35+
36+
/// Get the pbxproject's object version.
37+
#[must_use]
38+
pub fn object_version(&self) -> u8 {
39+
self.object_version
40+
}
41+
42+
/// Get a reference to the pbxproject's classes.
43+
#[must_use]
44+
pub fn classes(&self) -> &PBXHashMap {
45+
&self.classes
46+
}
47+
48+
/// Get a reference to the pbxproject's root object reference.
49+
#[must_use]
50+
pub fn root_object_reference(&self) -> &str {
51+
self.root_object_reference.as_ref()
52+
}
53+
54+
/// Get Root PBXProject
55+
pub fn root_project(&self) -> PBXProject {
56+
self.objects
57+
.projects()
58+
.into_iter()
59+
.find(|o| o.id == self.root_object_reference())
60+
.unwrap()
61+
}
62+
63+
/// Get root group
64+
pub fn root_group(&self) -> PBXFSReference {
65+
self.root_project().main_group
66+
}
67+
68+
/// Get a reference to the pbxroot object's objects.
69+
#[must_use]
70+
pub fn objects(&self) -> &PBXObjectCollection {
71+
&self.objects
72+
}
73+
74+
/// Get a mutable reference to the pbxroot object's objects.
75+
#[must_use]
76+
pub fn objects_mut(&mut self) -> &mut PBXObjectCollection {
77+
&mut self.objects
78+
}
79+
}
80+
81+
impl TryFrom<PBXHashMap> for PBXRootObject {
82+
type Error = anyhow::Error;
83+
fn try_from(mut map: PBXHashMap) -> Result<Self> {
84+
let archive_version = map.try_remove_number("archiveVersion")? as u8;
85+
let object_version = map.try_remove_number("objectVersion")? as u8;
86+
let classes = map.try_remove_object("classes").unwrap_or_default();
87+
let root_object_reference = map.try_remove_string("rootObject")?;
88+
let objects = PBXObjectCollection(
89+
map.try_remove_object("objects")?
90+
.0
91+
.into_iter()
92+
.map(|(k, v)| (k, v.try_into_object().unwrap()))
93+
.collect(),
94+
);
95+
96+
Ok(Self {
97+
archive_version,
98+
object_version,
99+
classes,
100+
objects,
101+
root_object_reference,
102+
})
103+
}
104+
}
105+
106+
impl TryFrom<&str> for PBXRootObject {
107+
type Error = anyhow::Error;
108+
fn try_from(content: &str) -> Result<Self> {
109+
use crate::pbxproj::pest::PBXProjectParser;
110+
111+
PBXProjectParser::try_from_str(content)?.pipe(Self::try_from)
112+
}
113+
}
114+
115+
impl TryFrom<String> for PBXRootObject {
116+
type Error = anyhow::Error;
117+
fn try_from(content: String) -> Result<Self> {
118+
Self::try_from(content.as_str())
119+
}
120+
}
121+
122+
impl TryFrom<&Path> for PBXRootObject {
123+
type Error = anyhow::Error;
124+
125+
fn try_from(value: &Path) -> Result<Self> {
126+
std::fs::read_to_string(&value)
127+
.map_err(|e| anyhow::anyhow!("PBXProjectData from path {value:?}: {e}"))?
128+
.pipe(TryFrom::try_from)
129+
}
130+
}
131+
132+
impl TryFrom<PathBuf> for PBXRootObject {
133+
type Error = anyhow::Error;
134+
135+
fn try_from(value: PathBuf) -> Result<Self> {
136+
Self::try_from(value.as_path())
137+
}
138+
}
139+
140+
#[test]
141+
fn test_demo1_representation() {
142+
let test_content = include_str!("../../tests/samples/demo1.pbxproj");
143+
let project = PBXRootObject::try_from(test_content).unwrap();
144+
let targets = project.targets();
145+
146+
assert_eq!(1, targets.len());
147+
assert_eq!(&PBXTargetKind::Native, targets[0].kind);
148+
assert_eq!(Some(&String::from("Wordle")), targets[0].product_name);
149+
assert_eq!(Some(&String::from("Wordle")), targets[0].name);
150+
assert_eq!(PBXProductType::Application, targets[0].product_type);
151+
assert_eq!(None, targets[0].build_tool_path);
152+
assert_eq!(None, targets[0].build_arguments_string);
153+
assert_eq!(None, targets[0].build_working_directory);
154+
assert_eq!(None, targets[0].pass_build_settings_in_environment);
155+
assert_eq!(3, targets[0].build_phases.len());
156+
assert_eq!(
157+
vec![
158+
(&PBXBuildPhaseKind::Sources, 12), // 12
159+
(&PBXBuildPhaseKind::Resources, 3), // 3
160+
(&PBXBuildPhaseKind::Frameworks, 1) // 1
161+
],
162+
targets[0]
163+
.build_phases
164+
.iter()
165+
.map(|phase| (&phase.kind, phase.files.len()))
166+
.collect::<Vec<_>>()
167+
);
168+
169+
assert_eq!(1, project.projects().len());
170+
171+
let root_group = project.root_group();
172+
assert_eq!(17, project.files().len());
173+
println!("{:#?}", root_group.children);
174+
assert_eq!(3, root_group.children.len());
175+
assert_eq!(None, root_group.name);
176+
assert_eq!(None, root_group.path);
177+
}
178+
179+
#[cfg(test)]
180+
macro_rules! test_demo_file {
181+
($name:expr) => {{
182+
let (root, name) = (env!("CARGO_MANIFEST_DIR"), stringify!($name));
183+
let path = format!("{root}/tests/samples/{name}.pbxproj");
184+
let file = crate::pbxproj::PBXRootObject::try_from(std::path::PathBuf::from(path));
185+
if file.is_err() {
186+
println!("Error: {:#?}", file.as_ref().unwrap_err())
187+
}
188+
assert!(file.is_ok());
189+
file.unwrap()
190+
}};
191+
}
192+
193+
#[cfg(test)]
194+
mod tests {
195+
macro_rules! test_samples {
196+
($($name:ident),*) => {
197+
$(#[test]
198+
fn $name() {
199+
test_demo_file!($name);
200+
})*
201+
};
202+
}
203+
204+
test_samples![demo1, demo2, demo3, demo4, demo5, demo6, demo7, demo8, demo9];
205+
}

src/pbxproj/object/build/config.rs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,37 @@
1-
use crate::pbxproj::{PBXHashMap, PBXObjectCollection, PBXObjectExt, PBXRootObject};
2-
use std::{cell::RefCell, rc::Weak};
1+
use crate::pbxproj::*;
32

43
/// [`PBXObject`] specifying build configurations
54
///
65
/// [`PBXObject`]: crate::pbxproj::PBXObject
76
#[derive(Debug, derive_new::new)]
8-
pub struct XCBuildConfiguration {
7+
pub struct XCBuildConfiguration<'a> {
8+
/// ID Reference
9+
pub id: String,
910
/// The configuration name.
10-
pub name: String,
11+
pub name: &'a String,
1112
/// A map of build settings.
12-
pub build_settings: PBXHashMap,
13-
/// Base xcconfig file reference.
14-
base_configuration_reference: Option<String>,
15-
objects: Weak<RefCell<PBXObjectCollection>>,
13+
pub build_settings: &'a PBXHashMap,
14+
/// Base xcconfig file.
15+
pub base_configuration: Option<PBXFSReference<'a>>,
1616
}
1717

18-
impl XCBuildConfiguration {
19-
/// GGet Base xcconfig file reference.
20-
pub fn base_configuration(&self, _data: PBXRootObject) -> Option<()> {
21-
todo!()
22-
}
23-
24-
/// Base xcconfig file reference.
25-
pub fn set_base_configuration(&mut self, reference: Option<String>) -> Option<String> {
26-
let old = self.base_configuration_reference.take();
27-
self.base_configuration_reference = reference;
28-
old
29-
}
30-
}
31-
32-
impl PBXObjectExt for XCBuildConfiguration {
33-
fn from_hashmap(
34-
mut value: PBXHashMap,
35-
objects: Weak<RefCell<PBXObjectCollection>>,
18+
impl<'a> AsPBXObject<'a> for XCBuildConfiguration<'a> {
19+
fn as_pbx_object(
20+
id: String,
21+
value: &'a PBXHashMap,
22+
objects: &'a PBXObjectCollection,
3623
) -> anyhow::Result<Self>
3724
where
38-
Self: Sized,
25+
Self: Sized + 'a,
3926
{
4027
Ok(Self {
41-
name: value.try_remove_string("name")?,
42-
build_settings: value.try_remove_object("buildSettings")?,
43-
base_configuration_reference: value.remove_string("base_configuration_reference"),
44-
objects,
28+
id,
29+
name: value.try_get_string("name")?,
30+
build_settings: value.try_get_object("buildSettings")?,
31+
base_configuration: value
32+
.get_value("baseConfigurationReference")
33+
.and_then(|v| v.as_string())
34+
.and_then(|key| objects.get(key)),
4535
})
4636
}
47-
48-
fn to_hashmap(&self) -> PBXHashMap {
49-
todo!()
50-
}
5137
}

0 commit comments

Comments
 (0)