You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/03/28 21:06:45 UTC
git commit: CB-6344: Specify after which sibling to add
config-changes in plugin.xml
Repository: cordova-plugman
Updated Branches:
refs/heads/master 2f18534b7 -> ac1160d47
CB-6344: Specify after which sibling to add config-changes in plugin.xml
* refactor munges - now they are in a more extensible form (munge.files[file].parents[parent][index] { xml, count, after })
* add an after parameter to graftXML which contains a list of possible predecessor elements separated by semicolon
* fix unit tests and add new ones
github: close #68
Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/ac1160d4
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/ac1160d4
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/ac1160d4
Branch: refs/heads/master
Commit: ac1160d470db03c2d2de2bbd7ad0ece336cf0394
Parents: 2f18534
Author: Martin Bektchiev <ma...@telerik.com>
Authored: Wed Mar 26 10:15:39 2014 +0200
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Fri Mar 28 16:03:48 2014 -0400
----------------------------------------------------------------------
spec/platforms/wp8.spec.js | 21 +++
spec/plugins/DummyPlugin/plugin.xml | 22 +++
spec/util/config-changes.spec.js | 70 +++++-----
spec/util/xml-helpers.spec.js | 27 ++++
src/util/config-changes.js | 228 +++++++++++++++++++++----------
src/util/xml-helpers.js | 24 +++-
6 files changed, 285 insertions(+), 107 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ac1160d4/spec/platforms/wp8.spec.js
----------------------------------------------------------------------
diff --git a/spec/platforms/wp8.spec.js b/spec/platforms/wp8.spec.js
index 398e567..2b0f265 100644
--- a/spec/platforms/wp8.spec.js
+++ b/spec/platforms/wp8.spec.js
@@ -70,6 +70,11 @@ describe('wp8 project handler', function() {
});
describe('installation', function() {
+ var done;
+ function installPromise(f) {
+ done = false;
+ f.then(function() { done = true; }, function(err) { done = err; });
+ }
beforeEach(function() {
shell.mkdir('-p', temp);
});
@@ -102,6 +107,22 @@ describe('wp8 project handler', function() {
}).toThrow('"' + target + '" already exists!');
});
});
+ describe('of <config-changes> elements', function() {
+ beforeEach(function() {
+ shell.cp('-rf', path.join(wp8_project, '*'), temp);
+ });
+ it('should process and pass the after parameter to graftXML', function () {
+ var graftXML = spyOn(xml_helpers, 'graftXML').andCallThrough();
+
+ runs(function () { installPromise(install('wp8', temp, dummyplugin, plugins_dir, {})); });
+ waitsFor(function () { return done; }, 'install promise never resolved', 500);
+ runs(function () {
+ expect(graftXML).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Array), "/Deployment/App", "Tokens");
+ expect(graftXML).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Array), "/Deployment/App/Extensions", "Extension");
+ expect(graftXML).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Array), "/Deployment/App/Extensions", "FileTypeAssociation;Extension");
+ });
+ });
+ });
});
describe('uninstallation', function() {
http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ac1160d4/spec/plugins/DummyPlugin/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/plugins/DummyPlugin/plugin.xml b/spec/plugins/DummyPlugin/plugin.xml
index bc71119..dcec3bf 100644
--- a/spec/plugins/DummyPlugin/plugin.xml
+++ b/spec/plugins/DummyPlugin/plugin.xml
@@ -155,6 +155,28 @@
<feature id="dummyPlugin" required="true" version="1.0.0.0"/>
</config-file>
+ <config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App" after="Tokens">
+ <Extensions />
+ </config-file>
+
+ <config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Extensions" after="Extension">
+ <Extension ExtensionName="DummyExtension1" ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5661}" TaskID="_default" ExtraFile="Extensions\\Extras.xml" />
+ <Extension ExtensionName="DummyExtension2" ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5661}" TaskID="_default" ExtraFile="Extensions\\Extras.xml" />
+ </config-file>
+
+ <config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Extensions" after="FileTypeAssociation;Extension">
+ <FileTypeAssociation TaskID="_default" Name="DummyFileType1" NavUriFragment="fileToken=%s">
+ <SupportedFileTypes>
+ <FileType ContentType="application/dummy1">.dummy1</FileType>
+ </SupportedFileTypes>
+ </FileTypeAssociation>
+ <FileTypeAssociation TaskID="_default" Name="DummyFileType2" NavUriFragment="fileToken=%s">
+ <SupportedFileTypes>
+ <FileType ContentType="application/dummy2">.dummy2</FileType>
+ </SupportedFileTypes>
+ </FileTypeAssociation>
+ </config-file>
+
<source-file src="src/wp8/DummyPlugin.cs"/>
<js-module src="www/dummyplugin.js" name="Dummy">
<clobbers target="dummy" />
http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ac1160d4/spec/util/config-changes.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/config-changes.spec.js b/spec/util/config-changes.spec.js
index 3c3c918..2efae84 100644
--- a/spec/util/config-changes.spec.js
+++ b/spec/util/config-changes.spec.js
@@ -118,7 +118,10 @@ describe('config-changes module', function() {
});
it('should return the json file if it exists', function() {
var filepath = path.join(plugins_dir, 'android.json');
- var json = {prepare_queue:{installed:[],uninstalled:[]},config_munge:{somechange:"blah"},installed_plugins:{}};
+ var json = {
+ prepare_queue: {installed: [], uninstalled: []},
+ config_munge: {files: {"some_file": {parents: {"some_parent": [{"xml": "some_change", "count": 1}]}}}},
+ installed_plugins: {}};
fs.writeFileSync(filepath, JSON.stringify(json), 'utf-8');
var cfg = configChanges.get_platform_json(plugins_dir, 'android');
expect(JSON.stringify(json)).toEqual(JSON.stringify(cfg));
@@ -144,64 +147,65 @@ describe('config-changes module', function() {
var xml;
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(dummyplugin, {});
- expect(munge['AndroidManifest.xml']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest/application']).toBeDefined();
+ expect(munge.files['AndroidManifest.xml']).toBeDefined();
+ expect(munge.files['AndroidManifest.xml'].parents['/manifest/application']).toBeDefined();
xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="AndroidManifest.xml"]'))).write({xml_declaration:false});
xml = innerXML(xml);
- expect(munge['AndroidManifest.xml']['/manifest/application'][xml]).toEqual(1);
- expect(munge['res/xml/plugins.xml']).toBeDefined();
- expect(munge['res/xml/plugins.xml']['/plugins']).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest/application', xml).count).toEqual(1);
+ expect(munge.files['res/xml/plugins.xml']).toBeDefined();
+ expect(munge.files['res/xml/plugins.xml'].parents['/plugins']).toBeDefined();
xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="res/xml/plugins.xml"]'))).write({xml_declaration:false});
xml = innerXML(xml);
- expect(munge['res/xml/plugins.xml']['/plugins'][xml]).toEqual(1);
- expect(munge['res/xml/config.xml']).toBeDefined();
- expect(munge['res/xml/config.xml']['/cordova/plugins']).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'res/xml/plugins.xml', '/plugins', xml).count).toEqual(1);
+ expect(munge.files['res/xml/config.xml']).toBeDefined();
+ expect(munge.files['res/xml/config.xml'].parents['/cordova/plugins']).toBeDefined();
xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="res/xml/config.xml"]'))).write({xml_declaration:false});
xml = innerXML(xml);
- expect(munge['res/xml/config.xml']['/cordova/plugins'][xml]).toEqual(1);
+ expect(configChanges.get_munge_change(munge, 'res/xml/config.xml', '/cordova/plugins', xml).count).toEqual(1);
});
it('should split out multiple children of config-file elements into individual leaves', function() {
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(childrenplugin, {});
- expect(munge['AndroidManifest.xml']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="android.permission.READ_PHONE_STATE" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="android.permission.INTERNET" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="android.permission.GET_ACCOUNTS" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="android.permission.WAKE_LOCK" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" android:protectionLevel="signature" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" />']).toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />']).toBeDefined();
+ expect(munge.files['AndroidManifest.xml']).toBeDefined();
+ expect(munge.files['AndroidManifest.xml'].parents['/manifest']).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.READ_PHONE_STATE" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.INTERNET" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.GET_ACCOUNTS" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.WAKE_LOCK" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" android:protectionLevel="signature" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />')).toBeDefined();
});
it('should not use xml comments as config munge leaves', function() {
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(childrenplugin, {});
- expect(munge['AndroidManifest.xml']['/manifest']['<!--library-->']).not.toBeDefined();
- expect(munge['AndroidManifest.xml']['/manifest']['<!-- GCM connects to Google Services. -->']).not.toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<!--library-->')).not.toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<!-- GCM connects to Google Services. -->')).not.toBeDefined();
});
it('should increment config heirarchy leaves if dfferent config-file elements target the same file + selector + xml', function() {
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(configplugin, {});
- expect(munge['res/xml/config.xml']['/widget']['<poop />']).toEqual(2);
+ expect(configChanges.get_munge_change(munge, 'res/xml/config.xml', '/widget', '<poop />').count).toEqual(2);
});
it('should take into account interpolation variables', function() {
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(childrenplugin, {PACKAGE_NAME:'ca.filmaj.plugins'});
- expect(munge['AndroidManifest.xml']['/manifest']['<uses-permission android:name="ca.filmaj.plugins.permission.C2D_MESSAGE" />']).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="ca.filmaj.plugins.permission.C2D_MESSAGE" />')).toBeDefined();
});
it('should create munges for platform-agnostic config.xml changes', function() {
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(dummyplugin, {});
- expect(munge['config.xml']['/*']['<access origin="build.phonegap.com" />']).toBeDefined();
- expect(munge['config.xml']['/*']['<access origin="s3.amazonaws.com" />']).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'config.xml', '/*', '<access origin="build.phonegap.com" />')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'config.xml', '/*', '<access origin="s3.amazonaws.com" />')).toBeDefined();
});
it('should automatically add on app java identifier as PACKAGE_NAME variable for android config munges', function() {
shell.cp('-rf', android_two_project, temp);
var munger = new configChanges.PlatformMunger('android', temp, 'unused');
var munge = munger.generate_plugin_config_munge(varplugin, {});
var expected_xml = '<package>com.alunny.childapp</package>';
- expect(munge['AndroidManifest.xml']['/manifest'][expected_xml]).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', expected_xml)).toBeDefined();
});
});
@@ -213,16 +217,16 @@ describe('config-changes module', function() {
var munger = new configChanges.PlatformMunger('ios', temp, 'unused');
var munge = munger.generate_plugin_config_munge(varplugin, {});
var expected_xml = '<cfbundleid>com.example.friendstring</cfbundleid>';
- expect(munge['config.xml']['/widget'][expected_xml]).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'config.xml', '/widget', expected_xml)).toBeDefined();
});
it('should special case framework elements for ios', function() {
var munger = new configChanges.PlatformMunger('ios', temp, 'unused');
var munge = munger.generate_plugin_config_munge(cbplugin, {});
- expect(munge['framework']).toBeDefined();
- expect(munge['framework']['libsqlite3.dylib']['false']).toBeDefined();
- expect(munge['framework']['social.framework']['true']).toBeDefined();
- expect(munge['framework']['music.framework']['false']).toBeDefined();
- expect(munge['framework']['Custom.framework']).not.toBeDefined();
+ expect(munge.files['framework']).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'framework', 'libsqlite3.dylib', 'false')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'framework', 'social.framework', 'true')).toBeDefined();
+ expect(configChanges.get_munge_change(munge, 'framework', 'music.framework', 'false')).toBeDefined();
+ expect(munge.files['framework'].parents['Custom.framework']).not.toBeDefined();
});
});
});
http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ac1160d4/spec/util/xml-helpers.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/xml-helpers.spec.js b/spec/util/xml-helpers.spec.js
index 5def06e..edcdedb 100644
--- a/spec/util/xml-helpers.spec.js
+++ b/spec/util/xml-helpers.spec.js
@@ -139,5 +139,32 @@ describe('xml-helpers', function(){
selector= "/bookstore/book[price>35]/title";
expect(xml_helpers.graftXML(doc, children, selector)).toBe(false);
});
+
+ it('appends children after the specified sibling', function () {
+ var doc = new et.ElementTree(et.XML('<widget><A/><B/><C/></widget>')),
+ children = [et.XML('<B id="new"/>'), et.XML('<B id="new2"/>')],
+ selector= "/widget",
+ after= "B;A";
+ expect(xml_helpers.graftXML(doc, children, selector, after)).toBe(true);
+ expect(et.tostring(doc.getroot())).toContain('<B /><B id="new" /><B id="new2" />');
+ });
+
+ it('appends children after the 2nd priority sibling if the 1st one is missing', function () {
+ var doc = new et.ElementTree(et.XML('<widget><A/><C/></widget>')),
+ children = [et.XML('<B id="new"/>'), et.XML('<B id="new2"/>')],
+ selector= "/widget",
+ after= "B;A";
+ expect(xml_helpers.graftXML(doc, children, selector, after)).toBe(true);
+ expect(et.tostring(doc.getroot())).toContain('<A /><B id="new" /><B id="new2" />');
+ });
+
+ it('inserts children at the beginning if specified sibling is missing', function () {
+ var doc = new et.ElementTree(et.XML('<widget><B/><C/></widget>')),
+ children = [et.XML('<A id="new"/>'), et.XML('<A id="new2"/>')],
+ selector= "/widget",
+ after= "A";
+ expect(xml_helpers.graftXML(doc, children, selector, after)).toBe(true);
+ expect(et.tostring(doc.getroot())).toContain('<widget><A id="new" /><A id="new2" />');
+ });
});
});
http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ac1160d4/src/util/config-changes.js
----------------------------------------------------------------------
diff --git a/src/util/config-changes.js b/src/util/config-changes.js
index 989a582..d09fcf1 100644
--- a/src/util/config-changes.js
+++ b/src/util/config-changes.js
@@ -39,6 +39,7 @@ var fs = require('fs'),
bplist = require('bplist-parser'),
xcode = require('xcode'),
et = require('elementtree'),
+ _ = require('underscore'),
xml_helpers = require('./../util/xml-helpers'),
platforms = require('./../platforms'),
events = require('./../events'),
@@ -77,6 +78,11 @@ exports.process = function(plugins_dir, project_dir, platform) {
munger.process();
munger.save_all();
};
+
+exports.get_munge_change = function(munge, keys) {
+ return deep_find.apply(null, arguments);
+}
+
/******************************************************************************/
@@ -126,19 +132,20 @@ PlatformMunger.prototype.apply_file_munge = PlatformMunger_apply_file_munge;
function PlatformMunger_apply_file_munge(file, munge, remove) {
var self = this;
var xml_child;
-
+
if ( file === 'framework' && self.platform === 'ios' ) {
// ios pbxproj file
var pbxproj = self.config_keeper.get(self.project_dir, self.platform, 'framework');
- for (var src in munge) {
- for (xml_child in munge[src]) {
+ for (var src in munge.parents) {
+ for (xml_child in munge.parents[src]) {
+ var xml = munge.parents[src][xml_child].xml;
// Only add the framework if it's not a cordova-ios core framework
if (keep_these_frameworks.indexOf(src) == -1) {
// xml_child in this case is whether the framework should use weak or not
if (remove) {
pbxproj.data.removeFramework(src);
} else {
- pbxproj.data.addFramework(src, {weak: (xml_child === 'true')});
+ pbxproj.data.addFramework(src, {weak: (xml === 'true')});
}
pbxproj.is_changed = true;
}
@@ -146,13 +153,13 @@ function PlatformMunger_apply_file_munge(file, munge, remove) {
}
} else {
// all other types of files
- for (var selector in munge) {
- for (xml_child in munge[selector]) {
+ for (var selector in munge.parents) {
+ for (xml_child in munge.parents[selector]) {
// this xml child is new, graft it (only if config file exists)
var config_file = self.config_keeper.get(self.project_dir, self.platform, file);
if (config_file.exists) {
- if (remove) config_file.prune_child(selector, xml_child);
- else config_file.graft_child(selector, xml_child);
+ if (remove) config_file.prune_child(selector, munge.parents[selector][xml_child]);
+ else config_file.graft_child(selector, munge.parents[selector][xml_child]);
}
}
}
@@ -173,7 +180,7 @@ function remove_plugin_changes(plugin_name, plugin_id, is_top_level) {
var global_munge = platform_config.config_munge;
var munge = decrement_munge(global_munge, config_munge);
- for (var file in munge) {
+ for (var file in munge.files) {
if (file == 'plugins-plist' && self.platform == 'ios') {
// TODO: remove this check and <plugins-plist> sections in spec/plugins/../plugin.xml files.
events.emit(
@@ -183,7 +190,7 @@ function remove_plugin_changes(plugin_name, plugin_id, is_top_level) {
);
continue;
}
- self.apply_file_munge(file, munge[file], /* remove = */ true);
+ self.apply_file_munge(file, munge.files[file], /* remove = */ true);
}
// Remove from installed_plugins
@@ -223,7 +230,7 @@ function add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increme
munge = config_munge;
}
- for (var file in munge) {
+ for (var file in munge.files) {
// TODO: remove this warning some time after 3.4 is out.
if (file == 'plugins-plist' && self.platform == 'ios') {
events.emit(
@@ -233,7 +240,7 @@ function add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increme
);
continue;
}
- self.apply_file_munge(file, munge[file]);
+ self.apply_file_munge(file, munge.files[file]);
}
// Move to installed_plugins if it is a top-level plugin
@@ -257,7 +264,7 @@ function reapply_global_munge () {
var platform_config = exports.get_platform_json(self.plugins_dir, self.platform);
var global_munge = platform_config.config_munge;
- for (var file in global_munge) {
+ for (var file in global_munge.files) {
// TODO: remove this warning some time after 3.4 is out.
if (file == 'plugins-plist' && self.platform == 'ios') {
events.emit(
@@ -268,7 +275,7 @@ function reapply_global_munge () {
continue;
}
// TODO: This is mostly file IO and can run in parallel since each file is independent.
- self.apply_file_munge(file, global_munge[file]);
+ self.apply_file_munge(file, global_munge.files[file]);
}
}
@@ -285,7 +292,7 @@ function generate_plugin_config_munge(plugin_dir, vars) {
vars['PACKAGE_NAME'] = self.platform_handler.package_name(self.project_dir);
}
- var munge = {};
+ var munge = { files: {} };
var plugin_config = self.config_keeper.get(plugin_dir, '', 'plugin.xml');
var plugin_xml = plugin_config.data;
@@ -303,18 +310,10 @@ function generate_plugin_config_munge(plugin_dir, vars) {
frameworks.forEach(function(f) {
var custom = f.attrib['custom'];
if(!custom) {
- if (!munge['framework']) {
- munge['framework'] = {};
- }
var file = f.attrib['src'];
- var weak = ('true' == f.attrib['weak']);
- if (!munge['framework'][file]) {
- munge['framework'][file] = {};
- }
- if (!munge['framework'][file][weak]) {
- munge['framework'][file][weak] = 0;
- }
- munge['framework'][file][weak] += 1;
+ var weak = ('true' == f.attrib['weak']).toString();
+
+ deep_add(munge, 'framework', file, { xml: weak, count: 1 });
}
});
}
@@ -322,14 +321,9 @@ function generate_plugin_config_munge(plugin_dir, vars) {
changes.forEach(function(change) {
var target = change.attrib['target'];
var parent = change.attrib['parent'];
- if (!munge[target]) {
- munge[target] = {};
- }
- if (!munge[target][parent]) {
- munge[target][parent] = {};
- }
+ var after = change.attrib['after'];
var xmls = change.getchildren();
- xmls.forEach(function(xml) {
+ xmls.forEach(function(xml) {
// 1. stringify each xml
var stringified = (new et.ElementTree(xml)).write({xml_declaration:false});
// interp vars
@@ -340,16 +334,12 @@ function generate_plugin_config_munge(plugin_dir, vars) {
});
}
// 2. add into munge
- if (!munge[target][parent][stringified]) {
- munge[target][parent][stringified] = 0;
- }
- munge[target][parent][stringified] += 1;
+ deep_add(munge, target, parent, { xml: stringified, count: 1, after: after });
});
});
return munge;
}
-
// Go over the prepare queue an apply the config munges for each plugin
// that has been (un)installed.
PlatformMunger.prototype.process = PlatformMunger_process;
@@ -426,7 +416,7 @@ function get_platform_json(plugins_dir, platform) {
var filepath = path.join(plugins_dir, platform + '.json');
if (fs.existsSync(filepath)) {
- return JSON.parse(fs.readFileSync(filepath, 'utf-8'));
+ return fix_munge(JSON.parse(fs.readFileSync(filepath, 'utf-8')));
} else {
var config = {
prepare_queue:{installed:[], uninstalled:[]},
@@ -445,6 +435,28 @@ function save_platform_json(config, plugins_dir, platform) {
fs.writeFileSync(filepath, JSON.stringify(config, null, 4), 'utf-8');
}
+
+// convert a munge from the old format ([file][parent][xml] = count) to the current one
+function fix_munge(platform_config) {
+ var munge = platform_config.config_munge;
+ if (!munge.files) {
+ var new_munge = { files: {} };
+ for (var file in munge) {
+ for (var selector in munge[file]) {
+ for (var xml_child in munge[file][selector]) {
+ var val = parseInt(munge[file][selector][xml_child]);
+ for (var i = 0; i < val; i++) {
+ deep_add(new_munge, [file, selector, { xml: xml_child, count: val }]);
+ }
+ }
+ }
+ }
+ platform_config.config_munge = new_munge;
+ }
+
+ return platform_config;
+}
+
/**** END of ConfigKeeper ****/
@@ -528,14 +540,14 @@ function ConfigFile_graft_child(selector, xml_child) {
var filepath = self.filepath;
var result;
if (self.type === 'xml') {
- var xml_to_graft = [et.XML(xml_child)];
- result = xml_helpers.graftXML(self.data, xml_to_graft, selector);
+ var xml_to_graft = [et.XML(xml_child.xml)];
+ result = xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after);
if ( !result) {
throw new Error('grafting xml at selector "' + selector + '" from "' + filepath + '" during config install went bad :(');
}
} else {
// plist file
- result = plist_helpers.graftPLIST(self.data, xml_child, selector);
+ result = plist_helpers.graftPLIST(self.data, xml_child.xml, selector);
if ( !result ) {
throw new Error('grafting to plist "' + filepath + '" during config install went bad :(');
}
@@ -550,11 +562,11 @@ function ConfigFile_prune_child(selector, xml_child) {
var filepath = self.filepath;
var result;
if (self.type === 'xml') {
- var xml_to_graft = [et.XML(xml_child)];
+ var xml_to_graft = [et.XML(xml_child.xml)];
result = xml_helpers.pruneXML(self.data, xml_to_graft, selector);
} else {
// plist file
- result = plist_helpers.prunePLIST(self.data, xml_child, selector);
+ result = plist_helpers.prunePLIST(self.data, xml_child.xml, selector);
}
if ( !result) {
var err_msg = 'Pruning at selector "' + selector + '" from "' + filepath + '" went bad.';
@@ -650,21 +662,94 @@ function resolveConfigFilePath(project_dir, platform, file) {
* Munge object manipulations functions
******************************************************************************/
-// Increment obj[key1][key2]...[keyN] by val. If it
-// didn't exist, set it to val.
-function deep_add(obj, val, keys /* or key1, key2 .... */ ) {
+// add the count of [key1][key2]...[keyN] to obj
+// return true if it didn't exist before
+function deep_add(obj, keys /* or key1, key2 .... */ ) {
if ( !Array.isArray(keys) ) {
- keys = Array.prototype.slice.call(arguments, 2);
+ keys = Array.prototype.slice.call(arguments, 1);
}
- var k = keys[0];
+ return process_munge(obj, true/*createParents*/, function (parentArray, k) {
+ var found = _.find(parentArray, function(element) {
+ return element.xml == k.xml;
+ });
+ if (found) {
+ found.after = found.after || k.after;
+ found.count += k.count;
+ } else {
+ parentArray.push(k);
+ }
+ return !found;
+ }, keys);
+}
+
+// decrement the count of [key1][key2]...[keyN] from obj and remove if it reaches 0
+// return true if it was removed or not found
+function deep_remove(obj, keys /* or key1, key2 .... */ ) {
+ if ( !Array.isArray(keys) ) {
+ keys = Array.prototype.slice.call(arguments, 1);
+ }
+
+ var result = process_munge(obj, false/*createParents*/, function (parentArray, k) {
+ var index = -1;
+ var found = _.find(parentArray, function (element) {
+ index++;
+ return element.xml == k.xml;
+ });
+ if (found) {
+ found.count -= k.count;
+ if (found.count > 0) {
+ return false;
+ }
+ else {
+ parentArray.splice(index, 1);
+ }
+ }
+ return undefined;
+ }, keys);
+
+ return typeof result === "undefined" ? true : result;
+}
+
+// search for [key1][key2]...[keyN]
+// return the object or undefined if not found
+function deep_find(obj, keys /* or key1, key2 .... */ ) {
+ if ( !Array.isArray(keys) ) {
+ keys = Array.prototype.slice.call(arguments, 1);
+ }
+
+ return process_munge(obj, false/*createParents?*/, function (parentArray, k) {
+ return _.find(parentArray, function (element) {
+ return element.xml == (k.xml || k);
+ });
+ }, keys);
+}
+
+// Execute func passing it the parent array and the xmlChild key.
+// When createParents is true, add the file and parent items they are missing
+// When createParents is false, stop and return undefined if the file and/or parent items are missing
+
+function process_munge(obj, createParents, func, keys /* or key1, key2 .... */ ) {
+ if ( !Array.isArray(keys) ) {
+ keys = Array.prototype.slice.call(arguments, 1);
+ }
+ var k = keys[0];
if (keys.length == 1) {
- obj[k] = obj[k] || 0;
- obj[k] += val;
- return obj[k];
+ return func(obj, k);
+ } else if (keys.length == 2) {
+ if (!obj.parents[k] && !createParents) {
+ return undefined;
+ }
+ obj.parents[k] = obj.parents[k] || [];
+ return process_munge(obj.parents[k], createParents, func, keys.slice(1));
+ } else if (keys.length == 3){
+ if (!obj.files[k] && !createParents) {
+ return undefined;
+ }
+ obj.files[k] = obj.files[k] || { parents: {} };
+ return process_munge(obj.files[k], createParents, func, keys.slice(1));
} else {
- obj[k] = obj[k] || {};
- return deep_add(obj[k], val, keys.slice(1));
+ throw new Error("Invalid key format. Must contain at most 3 elements (file, parent, xmlChild).");
}
}
@@ -673,17 +758,17 @@ function deep_add(obj, val, keys /* or key1, key2 .... */ ) {
// Returns a munge object containing values that exist in munge
// but not in base.
function increment_munge(base, munge) {
- var diff = {};
+ var diff = { files: {} };
- for (var file in munge) {
- for (var selector in munge[file]) {
- for (var xml_child in munge[file][selector]) {
- var val = munge[file][selector][xml_child];
+ for (var file in munge.files) {
+ for (var selector in munge.files[file].parents) {
+ for (var xml_child in munge.files[file].parents[selector]) {
+ var val = munge.files[file].parents[selector][xml_child];
// if node not in base, add it to diff and base
// else increment it's value in base without adding to diff
- var new_val = deep_add(base, val, [file, selector, xml_child]);
- if ( val == new_val ) {
- deep_add(diff, val, file, selector, xml_child);
+ var newlyAdded = deep_add(base, [file, selector, val]);
+ if (newlyAdded) {
+ deep_add(diff, file, selector, val);
}
}
}
@@ -693,21 +778,20 @@ function increment_munge(base, munge) {
// Update the base munge object as
// base[file][selector][child] -= base[file][selector][child]
-// nodes that reached zero value are removed from base and the returned munge
+// nodes that reached zero value are removed from base and added to the returned munge
// object.
function decrement_munge(base, munge) {
- var zeroed = {};
+ var zeroed = { files: {} };
- for (var file in munge) {
- for (var selector in munge[file]) {
- for (var xml_child in munge[file][selector]) {
- var val = munge[file][selector][xml_child];
+ for (var file in munge.files) {
+ for (var selector in munge.files[file].parents) {
+ for (var xml_child in munge.files[file].parents[selector]) {
+ var val = munge.files[file].parents[selector][xml_child];
// if node not in base, add it to diff and base
// else increment it's value in base without adding to diff
- var new_val = deep_add(base, -val, [file, selector, xml_child]);
- if ( new_val <= 0) {
- deep_add(zeroed, val, file, selector, xml_child);
- delete base[file][selector][xml_child];
+ var removed = deep_remove(base, [file, selector, val]);
+ if (removed) {
+ deep_add(zeroed, file, selector, val);
}
}
}
http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ac1160d4/src/util/xml-helpers.js
----------------------------------------------------------------------
diff --git a/src/util/xml-helpers.js b/src/util/xml-helpers.js
index cfb914c..265025e 100644
--- a/src/util/xml-helpers.js
+++ b/src/util/xml-helpers.js
@@ -23,6 +23,7 @@
var fs = require('fs')
, path = require('path')
+ , _ = require('underscore')
, et = require('elementtree');
module.exports = {
@@ -72,7 +73,7 @@ module.exports = {
},
// adds node to doc at selector, creating parent if it doesn't exist
- graftXML: function(doc, nodes, selector) {
+ graftXML: function(doc, nodes, selector, after) {
var parent = resolveParent(doc, selector);
if (!parent) {
//Try to create the parent recursively if necessary
@@ -91,7 +92,11 @@ module.exports = {
nodes.forEach(function (node) {
// check if child is unique first
if (uniqueChild(node, parent)) {
- parent.append(node);
+ var children = parent.getchildren();
+ var insertIdx = after ? findInsertIdx(children, after) : children.length;
+
+ //TODO: replace with parent.insert after the bug in ElementTree is fixed
+ parent.getchildren().splice(insertIdx, 0, node);
}
});
@@ -174,3 +179,18 @@ function resolveParent(doc, selector) {
}
return parent;
}
+
+// Find the index at which to insert an entry. After is a ;-separated priority list
+// of tags after which the insertion should be made. E.g. If we need to
+// insert an element C, and the rule is that the order of children has to be
+// As, Bs, Cs. After will be equal to "C;B;A".
+
+function findInsertIdx(children, after) {
+ var childrenTags = children.map(function(child) { return child.tag; });
+ var afters = after.split(";");
+ var afterIndexes = afters.map(function(current) { return childrenTags.lastIndexOf(current); });
+ var foundIndex = _.find(afterIndexes, function(index) { return index != -1; });
+
+ //add to the beginning if no matching nodes are found
+ return typeof foundIndex === 'undefined' ? 0 : foundIndex+1;
+}