Merge branch 'feature/xcdatamodel' of https://github.com/IvanKarpan/node-xcode into IvanKarpan-feature/xcdatamodel
diff --git a/lib/pbxFile.js b/lib/pbxFile.js
index 4431f0d..c53b1ab 100644
--- a/lib/pbxFile.js
+++ b/lib/pbxFile.js
@@ -94,9 +94,14 @@
 }
 
 function detectGroup(fileRef) {
-    var filetype = fileRef.lastKnownFileType || fileRef.explicitFileType,
+    var extension = path.extname(fileRef.basename).substring(1),
+        filetype = fileRef.lastKnownFileType || fileRef.explicitFileType,
         groupName = GROUP_BY_FILETYPE[unquoted(filetype)];
 
+    if (extension === 'xcdatamodeld') {
+        return 'Sources';
+    }
+
     if (!groupName) {
         return DEFAULT_GROUP;
     }
@@ -154,6 +159,7 @@
     
     self = this;
 
+    this.basename = path.basename(filepath);
     this.lastKnownFileType = opt.lastKnownFileType || detectType(filepath);
     this.group = detectGroup(self);
 
@@ -163,7 +169,6 @@
         this.dirname = path.dirname(filepath);
     }
 
-    this.basename = path.basename(filepath);
     this.path = defaultPath(this, filepath);
     this.fileEncoding = this.defaultEncoding = opt.defaultEncoding || defaultEncoding(self);
 
diff --git a/lib/pbxProject.js b/lib/pbxProject.js
index 92c6886..a295fe2 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,27 @@
     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,
+            name: path.basename(file.path),
+            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,6 +827,14 @@
     return this.hash.project.objects['PBXNativeTarget'];
 }
 
+pbxProject.prototype.xcVersionGroupSection = function () {
+    if (typeof this.hash.project.objects['XCVersionGroup'] !== 'object') {
+        this.hash.project.objects['XCVersionGroup'] = {};
+    }
+
+    return this.hash.project.objects['XCVersionGroup'];
+}
+
 pbxProject.prototype.pbxXCConfigurationList = function() {
     return this.hash.project.objects['XCConfigurationList'];
 }
@@ -830,6 +860,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;
@@ -1704,5 +1750,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..00a1ac8
--- /dev/null
+++ b/test/dataModelDocument.js
@@ -0,0 +1,161 @@
+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.name, path.basename(singleDataModelFilePath));
+        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
diff --git a/test/pbxFile.js b/test/pbxFile.js
index 9a55b0a..ac876f3 100644
--- a/test/pbxFile.js
+++ b/test/pbxFile.js
@@ -50,6 +50,13 @@
         test.done();
     },
 
+    'should detect that a .xcdatamodel path means wrapper.xcdatamodel': function (test) {
+        var sourceFile = new pbxFile('dataModel.xcdatamodel');
+
+        test.equal('wrapper.xcdatamodel', sourceFile.lastKnownFileType);
+        test.done();
+    },
+
     'should allow lastKnownFileType to be overridden': function (test) {
         var sourceFile = new pbxFile('Plugins/ChildBrowser.m',
                 { lastKnownFileType: 'somestupidtype' });
@@ -73,6 +80,12 @@
         test.equal('Sources', sourceFile.group);
         test.done();
     },
+    'should be Sources for data model document files': function (test) {
+        var dataModelFile = new pbxFile('dataModel.xcdatamodeld');
+
+        test.equal('Sources', dataModelFile.group);
+        test.done();
+    },
     'should be Frameworks for frameworks': function (test) {
         var framework = new pbxFile('libsqlite3.dylib');