Added simple-plist package to parse plists
Added findTargetKey utility method
Renamed pbxVersionGroupSection to xcVersionGroupSection
Implemented addToXcVersionGroupSection
Implemented addDataModelDocument
Added dataModelDocument test
Added 2 data model documents for tests (single and multiple models)
diff --git a/lib/pbxProject.js b/lib/pbxProject.js
index f122638..1df2e43 100644
--- a/lib/pbxProject.js
+++ b/lib/pbxProject.js
@@ -8,6 +8,7 @@
pbxFile = require('./pbxFile'),
fs = require('fs'),
parser = require('./parser/pbxproj'),
+ plist = require('simple-plist'),
COMMENT_KEY = /_comment$/
function pbxProject(filename) {
@@ -507,6 +508,26 @@
return file;
}
+pbxProject.prototype.addToXcVersionGroupSection = function(file) {
+ if (!file.models || !file.currentModel) {
+ throw new Error("Cannot create a XCVersionGroup section from not a data model document file");
+ }
+
+ var commentKey = f("%s_comment", file.fileRef);
+
+ if (!this.xcVersionGroupSection()[file.fileRef]) {
+ this.xcVersionGroupSection()[file.fileRef] = {
+ isa: 'XCVersionGroup',
+ children: file.models.map(function (el) { return el.fileRef; }),
+ currentVersion: file.currentModel.fileRef,
+ path: file.path,
+ sourceTree: '"<group>"',
+ versionGroupType: 'wrapper.xcdatamodel'
+ };
+ this.xcVersionGroupSection()[commentKey] = path.basename(file.path);
+ }
+}
+
pbxProject.prototype.addToPluginsPbxGroup = function(file) {
var pluginsGroup = this.pbxGroupByName('Plugins');
pluginsGroup.children.push(pbxGroupChild(file));
@@ -805,7 +826,7 @@
return this.hash.project.objects['PBXNativeTarget'];
}
-pbxProject.prototype.pbxVersionGroupSection = function () {
+pbxProject.prototype.xcVersionGroupSection = function () {
if (typeof this.hash.project.objects['XCVersionGroup'] !== 'object') {
this.hash.project.objects['XCVersionGroup'] = {};
}
@@ -838,6 +859,22 @@
return this.pbxItemByComment(name, 'PBXNativeTarget');
}
+pbxProject.prototype.findTargetKey = function(name) {
+ var targets = this.hash.project.objects['PBXNativeTarget'];
+
+ for (var key in targets) {
+ // only look for comments
+ if (COMMENT_KEY.test(key)) continue;
+
+ var target = targets[key];
+ if (target.name === name) {
+ return key;
+ }
+ }
+
+ return null;
+}
+
pbxProject.prototype.pbxItemByComment = function(name, pbxSectionName) {
var section = this.hash.project.objects[pbxSectionName],
key, itemKey;
@@ -1712,5 +1749,61 @@
return target;
}
+pbxProject.prototype.addDataModelDocument = function(filePath, group, opt) {
+ if (!group) {
+ group = 'Resources';
+ }
+ if (!this.getPBXGroupByKey(group)) {
+ group = this.findPBXGroupKey({ name: group });
+ }
+
+ var file = new pbxFile(filePath, opt);
+
+ if (!file || this.hasFile(file.path)) return null;
+
+ file.fileRef = this.generateUuid();
+ this.addToPbxGroup(file, group);
+
+ if (!file) return false;
+
+ file.target = opt ? opt.target : undefined;
+ file.uuid = this.generateUuid();
+
+ this.addToPbxBuildFileSection(file);
+ this.addToPbxSourcesBuildPhase(file);
+
+ file.models = [];
+ var currentVersionName;
+ var modelFiles = fs.readdirSync(file.path);
+ for (var index in modelFiles) {
+ var modelFileName = modelFiles[index];
+ var modelFilePath = path.join(filePath, modelFileName);
+
+ if (modelFileName == '.xccurrentversion') {
+ currentVersionName = plist.readFileSync(modelFilePath)._XCCurrentVersionName;
+ continue;
+ }
+
+ var modelFile = new pbxFile(modelFilePath);
+ modelFile.fileRef = this.generateUuid();
+
+ this.addToPbxFileReferenceSection(modelFile);
+
+ file.models.push(modelFile);
+
+ if (currentVersionName && currentVersionName === modelFileName) {
+ file.currentModel = modelFile;
+ }
+ }
+
+ if (!file.currentModel) {
+ file.currentModel = file.models[0];
+ }
+
+ this.addToXcVersionGroupSection(file);
+
+ return file;
+}
+
module.exports = pbxProject;
diff --git a/package.json b/package.json
index fe18e2b..c19ac7d 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"name": "xcode",
"description": "parser for xcodeproj/project.pbxproj files",
"version": "0.8.2",
- "main":"index.js",
+ "main": "index.js",
"repository": {
"url": "https://github.com/alunny/node-xcode.git"
},
@@ -11,8 +11,9 @@
"node": ">=0.6.7"
},
"dependencies": {
+ "node-uuid":"1.3.3",
"pegjs":"0.6.2",
- "node-uuid":"1.3.3"
+ "simple-plist": "0.0.4"
},
"devDependencies": {
"nodeunit":"0.9.0"
diff --git a/test/dataModelDocument.js b/test/dataModelDocument.js
new file mode 100644
index 0000000..f91f35d
--- /dev/null
+++ b/test/dataModelDocument.js
@@ -0,0 +1,160 @@
+var jsonProject = require('./fixtures/full-project')
+ fullProjectStr = JSON.stringify(jsonProject),
+ path = require('path'),
+ pbx = require('../lib/pbxProject'),
+ pbxFile = require('../lib/pbxFile'),
+ proj = new pbx('.'),
+ singleDataModelFilePath = __dirname + '/fixtures/single-data-model.xcdatamodeld',
+ multipleDataModelFilePath = __dirname + '/fixtures/multiple-data-model.xcdatamodeld';
+
+function cleanHash() {
+ return JSON.parse(fullProjectStr);
+}
+
+exports.setUp = function (callback) {
+ proj.hash = cleanHash();
+ callback();
+}
+
+exports.dataModelDocument = {
+ 'should return a pbxFile': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath);
+
+ test.equal(newFile.constructor, pbxFile);
+ test.done()
+ },
+ 'should set a uuid on the pbxFile': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath);
+
+ test.ok(newFile.uuid);
+ test.done()
+ },
+ 'should set a fileRef on the pbxFile': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath);
+
+ test.ok(newFile.fileRef);
+ test.done()
+ },
+ 'should set an optional target on the pbxFile': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath, undefined, { target: target }),
+ target = proj.findTargetKey('TestApp');
+
+ test.equal(newFile.target, target);
+ test.done()
+ },
+ 'should populate the PBXBuildFile section with 2 fields': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath),
+ buildFileSection = proj.pbxBuildFileSection(),
+ bfsLength = Object.keys(buildFileSection).length;
+
+ test.equal(59 + 1, bfsLength);
+ test.ok(buildFileSection[newFile.uuid]);
+ test.ok(buildFileSection[newFile.uuid + '_comment']);
+
+ test.done();
+ },
+ 'should populate the PBXFileReference section with 2 fields for single model document': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath),
+ fileRefSection = proj.pbxFileReferenceSection(),
+ frsLength = Object.keys(fileRefSection).length;
+
+ test.equal(66 + 2, frsLength);
+ test.ok(fileRefSection[newFile.models[0].fileRef]);
+ test.ok(fileRefSection[newFile.models[0].fileRef + '_comment']);
+
+ test.done();
+ },
+ 'should populate the PBXFileReference section with 2 fields for each model of a model document': function (test) {
+ var newFile = proj.addDataModelDocument(multipleDataModelFilePath),
+ fileRefSection = proj.pbxFileReferenceSection(),
+ frsLength = Object.keys(fileRefSection).length;
+
+ test.equal(66 + 2 * 2, frsLength);
+ test.ok(fileRefSection[newFile.models[0].fileRef]);
+ test.ok(fileRefSection[newFile.models[0].fileRef + '_comment']);
+ test.ok(fileRefSection[newFile.models[1].fileRef]);
+ test.ok(fileRefSection[newFile.models[1].fileRef + '_comment']);
+
+ test.done();
+ },
+ 'should add to resources group by default': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath);
+ groupChildren = proj.pbxGroupByName('Resources').children,
+ found = false;
+
+ for (var index in groupChildren) {
+ if (groupChildren[index].comment === 'single-data-model.xcdatamodeld') {
+ found = true;
+ break;
+ }
+ }
+ test.ok(found);
+ test.done();
+ },
+ 'should add to group specified by key': function (test) {
+ var group = 'Frameworks',
+ newFile = proj.addDataModelDocument(singleDataModelFilePath, proj.findPBXGroupKey({ name: group }));
+ groupChildren = proj.pbxGroupByName(group).children;
+
+ var found = false;
+ for (var index in groupChildren) {
+ if (groupChildren[index].comment === path.basename(singleDataModelFilePath)) {
+ found = true;
+ break;
+ }
+ }
+ test.ok(found);
+ test.done();
+ },
+ 'should add to group specified by name': function (test) {
+ var group = 'Frameworks',
+ newFile = proj.addDataModelDocument(singleDataModelFilePath, group);
+ groupChildren = proj.pbxGroupByName(group).children;
+
+ var found = false;
+ for (var index in groupChildren) {
+ if (groupChildren[index].comment === path.basename(singleDataModelFilePath)) {
+ found = true;
+ break;
+ }
+ }
+ test.ok(found);
+ test.done();
+ },
+ 'should add to the PBXSourcesBuildPhase': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath),
+ sources = proj.pbxSourcesBuildPhaseObj();
+
+ test.equal(sources.files.length, 2 + 1);
+ test.done();
+ },
+ 'should create a XCVersionGroup section': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath),
+ xcVersionGroupSection = proj.xcVersionGroupSection();
+
+ test.ok(xcVersionGroupSection[newFile.fileRef]);
+ test.done();
+ },
+ 'should populate the XCVersionGroup comment correctly': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath),
+ xcVersionGroupSection = proj.xcVersionGroupSection(),
+ commentKey = newFile.fileRef + '_comment';
+
+ test.equal(xcVersionGroupSection[commentKey], path.basename(singleDataModelFilePath));
+ test.done();
+ },
+ 'should add the XCVersionGroup object correctly': function (test) {
+ var newFile = proj.addDataModelDocument(singleDataModelFilePath),
+ xcVersionGroupSection = proj.xcVersionGroupSection(),
+ xcVersionGroupEntry = xcVersionGroupSection[newFile.fileRef];
+
+ test.equal(xcVersionGroupEntry.isa, 'XCVersionGroup');
+ test.equal(xcVersionGroupEntry.children[0], newFile.models[0].fileRef);
+ test.equal(xcVersionGroupEntry.currentVersion, newFile.currentModel.fileRef);
+ test.equal(xcVersionGroupEntry.path, singleDataModelFilePath);
+ test.equal(xcVersionGroupEntry.sourceTree, '"<group>"');
+ test.equal(xcVersionGroupEntry.versionGroupType, 'wrapper.xcdatamodel');
+
+ test.done();
+ }
+}
diff --git a/test/fixtures/multiple-data-model.xcdatamodeld/.xccurrentversion b/test/fixtures/multiple-data-model.xcdatamodeld/.xccurrentversion
new file mode 100644
index 0000000..cb72aba
--- /dev/null
+++ b/test/fixtures/multiple-data-model.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>_XCCurrentVersionName</key>
+ <string>2.xcdatamodel</string>
+</dict>
+</plist>
diff --git a/test/fixtures/multiple-data-model.xcdatamodeld/1.xcdatamodel/contents b/test/fixtures/multiple-data-model.xcdatamodeld/1.xcdatamodel/contents
new file mode 100644
index 0000000..6bd3dcd
--- /dev/null
+++ b/test/fixtures/multiple-data-model.xcdatamodeld/1.xcdatamodel/contents
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 7.0">
+ <elements/>
+</model>
\ No newline at end of file
diff --git a/test/fixtures/multiple-data-model.xcdatamodeld/2.xcdatamodel/contents b/test/fixtures/multiple-data-model.xcdatamodeld/2.xcdatamodel/contents
new file mode 100644
index 0000000..6bd3dcd
--- /dev/null
+++ b/test/fixtures/multiple-data-model.xcdatamodeld/2.xcdatamodel/contents
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 7.0">
+ <elements/>
+</model>
\ No newline at end of file
diff --git a/test/fixtures/single-data-model.xcdatamodeld/SingleDataModel.xcdatamodel/contents b/test/fixtures/single-data-model.xcdatamodeld/SingleDataModel.xcdatamodel/contents
new file mode 100644
index 0000000..6bd3dcd
--- /dev/null
+++ b/test/fixtures/single-data-model.xcdatamodeld/SingleDataModel.xcdatamodel/contents
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 7.0">
+ <elements/>
+</model>
\ No newline at end of file