cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rkn...@apache.org
Subject cordova-lib git commit: CB-11023 Add edit-config functionality
Date Mon, 20 Jun 2016 17:26:49 GMT
Repository: cordova-lib
Updated Branches:
  refs/heads/master a4a2cbb9c -> 5fb8dffb2


CB-11023 Add edit-config functionality

 This closes #449


Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/5fb8dffb
Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/5fb8dffb
Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/5fb8dffb

Branch: refs/heads/master
Commit: 5fb8dffb2dc92e4d08d286432f23299da0e81249
Parents: a4a2cbb
Author: ktop <ktop500@gmail.com>
Authored: Thu Jun 16 16:48:16 2016 -0400
Committer: Richard Knoll <riknoll@microsoft.com>
Committed: Mon Jun 20 10:23:54 2016 -0700

----------------------------------------------------------------------
 .../spec/ConfigChanges/ConfigChanges.spec.js    |  85 ++++++++++++
 .../plugins/org.test.editconfigtest/plugin.xml  |  37 +++++
 .../org.test.editconfigtest_two/plugin.xml      |  37 +++++
 cordova-common/spec/util/xml-helpers.spec.js    | 135 +++++++++++++++++++
 .../src/ConfigChanges/ConfigChanges.js          | 108 +++++++++++++--
 cordova-common/src/ConfigChanges/ConfigFile.js  |  20 ++-
 cordova-common/src/ConfigChanges/munge-util.js  |   3 +
 cordova-common/src/PlatformJson.js              |   5 +-
 cordova-common/src/PluginInfo/PluginInfo.js     |  16 +++
 cordova-common/src/PluginManager.js             |   2 +-
 cordova-common/src/util/xml-helpers.js          | 124 ++++++++++++-----
 11 files changed, 525 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js
----------------------------------------------------------------------
diff --git a/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js b/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js
index df54fe1..1daaa4b 100644
--- a/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js
+++ b/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js
@@ -32,6 +32,8 @@ var configChanges = require('../../src/ConfigChanges/ConfigChanges'),
     childrenplugin = path.join(__dirname, '../fixtures/plugins/org.test.multiple-children'),
     shareddepsplugin = path.join(__dirname, '../fixtures/plugins/org.test.shareddeps'),
     configplugin = path.join(__dirname, '../fixtures/plugins/org.test.configtest'),
+    editconfigplugin = path.join(__dirname, '../fixtures/plugins/org.test.editconfigtest'),
+    editconfigplugin_two = path.join(__dirname, '../fixtures/plugins/org.test.editconfigtest_two'),
     varplugin = path.join(__dirname, '../fixtures/plugins/com.adobe.vars'),
     plistplugin = path.join(__dirname, '../fixtures/plugins/org.apache.plist'),
     android_two_project = path.join(__dirname, '../fixtures/projects/android_two/*'),
@@ -273,6 +275,70 @@ describe('config-changes module', function() {
                     munger.process(plugins_dir);
                     expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'),
'utf-8');
                 });
+                it('should call graftXMLMerge for every new config munge with mode \'merge\'
it introduces', function() {
+                    shell.cp('-rf', editconfigplugin, plugins_dir);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest',
{});
+
+                    var spy = spyOn(xml_helpers, 'graftXMLMerge').andReturn(true);
+
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson,
pluginInfoProvider);
+                    munger.process(plugins_dir);
+                    expect(spy.calls.length).toEqual(1);
+                    expect(spy.argsForCall[0][2]).toEqual('/manifest/application/activity[@android:name=\'org.test.DroidGap\']');
+                });
+                it('should call graftXMLMerge with --force for every new config munge with
mode \'merge\' it introduces', function() {
+                    shell.cp('-rf', editconfigplugin, plugins_dir);
+                    shell.cp('-rf', editconfigplugin_two, plugins_dir);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest',
{});
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest_two',
{}, true, true);
+
+                    var spy = spyOn(xml_helpers, 'graftXMLMerge').andReturn(true);
+
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson,
pluginInfoProvider);
+                    munger.process(plugins_dir);
+                    expect(spy.calls.length).toEqual(2);
+                    expect(spy.argsForCall[0][2]).toEqual('/manifest/application/activity[@android:name=\'org.test.DroidGap\']');
+                    expect(spy.argsForCall[1][2]).toEqual('/manifest/application/activity[@android:name=\'org.test.DroidGap\']');
+                });
+                it('should call graftXMLOverwrite for every new config munge with mode \'overwrite\'
it introduces', function() {
+                    shell.cp('-rf', editconfigplugin, plugins_dir);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest',
{});
+
+                    var spy = spyOn(xml_helpers, 'graftXMLOverwrite').andReturn(true);
+
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson,
pluginInfoProvider);
+                    munger.process(plugins_dir);
+                    expect(spy.calls.length).toEqual(1);
+                    expect(spy.argsForCall[0][2]).toEqual('/manifest/application/activity');
+                });
+                it('should call graftXMLOverwrite with --force for every new config munge
with mode \'overwrite\' it introduces', function() {
+                    shell.cp('-rf', editconfigplugin, plugins_dir);
+                    shell.cp('-rf', editconfigplugin_two, plugins_dir);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest',
{});
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest_two',
{}, true, true);
+
+                    var spy = spyOn(xml_helpers, 'graftXMLOverwrite').andReturn(true);
+
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson,
pluginInfoProvider);
+                    munger.process(plugins_dir);
+                    expect(spy.calls.length).toEqual(2);
+                    expect(spy.argsForCall[0][2]).toEqual('/manifest/application/activity');
+                    expect(spy.argsForCall[1][2]).toEqual('/manifest/application/activity[@android:name=\'ChildApp\']');
+                });
+                it('should not install plugin when there are edit-config conflicts', function()
{
+                    shell.cp('-rf', editconfigplugin, plugins_dir);
+                    shell.cp('-rf', editconfigplugin_two, plugins_dir);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest',
{});
+                    platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest_two',
{});
+
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson,
pluginInfoProvider);
+                    expect(function() {munger.process(plugins_dir);}).toThrow(new Error('There
was a conflict trying to modify attributes with <edit-config> in plugin org.test.editconfigtest_two.
The conflicting plugin, org.test.editconfigtest, already modified the same attributes. The
conflict must be resolved before org.test.editconfigtest_two can be added. You may use --force
to add the plugin and overwrite the conflicting attributes.'));
+                });
             });
             describe('of plist config files', function() {
                 it('should write empty string nodes with no whitespace', function() {
@@ -411,6 +477,25 @@ describe('config-changes module', function() {
                 expect(platformJson.root.prepare_queue.uninstalled.length).toEqual(0);
                 expect(platformJson.root.installed_plugins['com.adobe.vars']).not.toBeDefined();
             });
+            it('should call pruneXMLRestore for every config munge with mode \'merge\' or
\'overwrite\' it removes from the app', function() {
+                shell.cp('-rf', android_two_project, temp);
+                shell.cp('-rf', editconfigplugin, plugins_dir);
+
+                // Run through an "install"
+                var platformJson = PlatformJson.load(plugins_dir, 'android');
+                platformJson.addInstalledPluginToPrepareQueue('org.test.editconfigtest',
{});
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson,
pluginInfoProvider);
+                munger.process(plugins_dir);
+
+                // Now set up an uninstall and make sure pruneXMLMerge is called properly
+                platformJson.addUninstalledPluginToPrepareQueue('org.test.editconfigtest');
+                var spy = spyOn(xml_helpers, 'pruneXMLRestore').andReturn(true);
+                munger.process(plugins_dir);
+
+                expect(spy.calls.length).toEqual(2);
+                expect(spy.argsForCall[0][1]).toEqual('/manifest/application/activity[@android:name=\'org.test.DroidGap\']');
+                expect(spy.argsForCall[1][1]).toEqual('/manifest/application/activity');
+            });
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/spec/fixtures/plugins/org.test.editconfigtest/plugin.xml
----------------------------------------------------------------------
diff --git a/cordova-common/spec/fixtures/plugins/org.test.editconfigtest/plugin.xml b/cordova-common/spec/fixtures/plugins/org.test.editconfigtest/plugin.xml
new file mode 100644
index 0000000..b21e952
--- /dev/null
+++ b/cordova-common/spec/fixtures/plugins/org.test.editconfigtest/plugin.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="org.test.editconfigtest"
+    version="3.0.0">
+
+    <name>Test edit-config</name>
+
+    <!-- android -->
+    <platform name="android">
+        <edit-config file="AndroidManifest.xml" target="/manifest/application/activity[@android:name='org.test.DroidGap']"
mode="merge">
+            <activity android:enabled="true" android:configChanges="keyboardHidden"  />
+        </edit-config>
+        <edit-config file="AndroidManifest.xml" target="/manifest/application/activity"
mode="overwrite">
+            <activity android:name="ChildApp" android:label="@string/app_name" android:enabled="true"
/>
+        </edit-config>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/spec/fixtures/plugins/org.test.editconfigtest_two/plugin.xml
----------------------------------------------------------------------
diff --git a/cordova-common/spec/fixtures/plugins/org.test.editconfigtest_two/plugin.xml b/cordova-common/spec/fixtures/plugins/org.test.editconfigtest_two/plugin.xml
new file mode 100644
index 0000000..5a64034
--- /dev/null
+++ b/cordova-common/spec/fixtures/plugins/org.test.editconfigtest_two/plugin.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="org.test.editconfigtest_two"
+    version="3.0.0">
+
+    <name>Test edit-config with conflicts</name>
+
+    <!-- android -->
+    <platform name="android">
+        <edit-config file="AndroidManifest.xml" target="/manifest/application/activity[@android:name='org.test.DroidGap']"
mode="merge">
+            <activity android:enabled="true" android:configChanges="orientation|keyboardHidden"
 />
+        </edit-config>
+        <edit-config file="AndroidManifest.xml" target="/manifest/application/activity[@android:name='ChildApp']"
mode="overwrite">
+            <activity android:name="ChildApp" android:label="@string/app_name" android:enabled="false"
/>
+        </edit-config>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/spec/util/xml-helpers.spec.js
----------------------------------------------------------------------
diff --git a/cordova-common/spec/util/xml-helpers.spec.js b/cordova-common/spec/util/xml-helpers.spec.js
index 91128a6..24c6553 100644
--- a/cordova-common/spec/util/xml-helpers.spec.js
+++ b/cordova-common/spec/util/xml-helpers.spec.js
@@ -129,6 +129,59 @@ describe('xml-helpers', function(){
         });
     });
 
+    describe('pruneXMLRestore', function() {
+        var android_manifest_xml;
+
+        beforeEach(function() {
+            android_manifest_xml = xml_helpers.parseElementtreeSync(path.join(__dirname,
'../fixtures/projects/android/AndroidManifest.xml'));
+        });
+        it('should restore attributes at the specified selector', function() {
+            var xml = {
+                oldAttrib: {'android:icon': '@drawable/icon', 'android:label': '@string/app_name',
'android:debuggable': 'false'}
+            };
+            xml_helpers.pruneXMLRestore(android_manifest_xml, 'application', xml);
+            var applicationAttr = android_manifest_xml.find('application').attrib;
+            expect(Object.keys(applicationAttr).length).toEqual(3);
+            expect(applicationAttr['android:debuggable']).toEqual('false');
+        });
+        it('should do nothing if the old attributes cannot be found', function() {
+            var xml = {
+                notOldAttrib: {'android:icon': '@drawable/icon', 'android:label': '@string/app_name',
'android:debuggable': 'false'}
+            };
+            xml_helpers.pruneXMLRestore(android_manifest_xml, 'application', xml);
+            var applicationAttr = android_manifest_xml.find('application').attrib;
+            expect(Object.keys(applicationAttr).length).toEqual(3);
+            expect(applicationAttr['android:debuggable']).toEqual('true');
+        });
+        it('should be able to handle absolute selectors', function() {
+            var xml = {
+                oldAttrib: {'android:icon': '@drawable/icon', 'android:label': '@string/app_name',
'android:debuggable': 'false'}
+            };
+            xml_helpers.pruneXMLRestore(android_manifest_xml, '/manifest/application', xml);
+            var applicationAttr = android_manifest_xml.find('application').attrib;
+            expect(Object.keys(applicationAttr).length).toEqual(3);
+            expect(applicationAttr['android:debuggable']).toEqual('false');
+        });
+        it('should be able to handle absolute selectors with wildcards', function() {
+            var xml = {
+                oldAttrib: {'android:name': 'ChildApp', 'android:label': '@string/app_name',
'android:configChanges': 'orientation|keyboardHidden', 'android:enabled': 'true'}
+            };
+            xml_helpers.pruneXMLRestore(android_manifest_xml, '/*/*/activity', xml);
+            var activityAttr = android_manifest_xml.find('application/activity').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(4);
+            expect(activityAttr['android:enabled']).toEqual('true');
+        });
+        it('should be able to handle xpath selectors', function() {
+            var xml = {
+                oldAttrib: {'android:name': 'com.phonegap.DroidGap', 'android:label': '@string/app_name',
'android:configChanges': 'orientation|keyboardHidden', 'android:enabled': 'true'}
+            };
+            xml_helpers.pruneXMLRestore(android_manifest_xml, 'application/activity[@android:name=\"com.phonegap.DroidGap\"]',
xml);
+            var activityAttr = android_manifest_xml.find('application/activity[@android:name=\"com.phonegap.DroidGap\"]').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(4);
+            expect(activityAttr['android:enabled']).toEqual('true');
+        });
+    });
+
     describe('graftXML', function() {
         var config_xml, plugin_xml;
 
@@ -154,6 +207,88 @@ describe('xml-helpers', function(){
         });
     });
 
+    describe('graftXMLMerge', function() {
+        var plugin_xml, android_manifest_xml;
+
+        beforeEach(function() {
+            plugin_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '../fixtures/plugins/org.test.editconfigtest/plugin.xml'));
+            android_manifest_xml = xml_helpers.parseElementtreeSync(path.join(__dirname,
'../fixtures/projects/android/AndroidManifest.xml'));
+        });
+        it ('should merge attributes at specified selector', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"merge\"]').getchildren();
+            xml_helpers.graftXMLMerge(android_manifest_xml, children, 'application/activity[@android:name=\"com.phonegap.DroidGap\"]',
{});
+            var activityAttr = android_manifest_xml.find('application/activity[@android:name=\"com.phonegap.DroidGap\"]').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(4);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).toEqual('keyboardHidden');
+        });
+        it ('should be able to handle absolute selectors', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"merge\"]').getchildren();
+            xml_helpers.graftXMLMerge(android_manifest_xml, children, '/manifest/application/activity[@android:name=\"com.phonegap.DroidGap\"]',
{});
+            var activityAttr = android_manifest_xml.find('application/activity[@android:name=\"com.phonegap.DroidGap\"]').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(4);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).toEqual('keyboardHidden');
+        });
+        it ('should be able to handle absolute selectors with wildcards', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"merge\"]').getchildren();
+            xml_helpers.graftXMLMerge(android_manifest_xml, children, '/*/*/activity[@android:name=\"com.phonegap.DroidGap\"]',
{});
+            var activityAttr = android_manifest_xml.find('application/activity[@android:name=\"com.phonegap.DroidGap\"]').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(4);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).toEqual('keyboardHidden');
+        });
+        it ('should be able to handle xpath selectors', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"merge\"]').getchildren();
+            xml_helpers.graftXMLMerge(android_manifest_xml, children, 'application/activity[@android:name=\"com.phonegap.DroidGap\"]',
{});
+            var activityAttr = android_manifest_xml.find('application/activity[@android:name=\"com.phonegap.DroidGap\"]').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(4);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).toEqual('keyboardHidden');
+        });
+    });
+
+    describe('graftXMLOverwrite', function() {
+        var plugin_xml, android_manifest_xml;
+
+        beforeEach(function() {
+            plugin_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '../fixtures/plugins/org.test.editconfigtest/plugin.xml'));
+            android_manifest_xml = xml_helpers.parseElementtreeSync(path.join(__dirname,
'../fixtures/projects/android/AndroidManifest.xml'));
+        });
+        it ('should overwrite attributes at specified selector', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"overwrite\"]').getchildren();
+            xml_helpers.graftXMLOverwrite(android_manifest_xml, children, 'application/activity',
{});
+            var activityAttr = android_manifest_xml.find('application/activity').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(3);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).not.toBeDefined();
+        });
+        it ('should be able to handle absolute selectors', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"overwrite\"]').getchildren();
+            xml_helpers.graftXMLOverwrite(android_manifest_xml, children, '/manifest/application/activity',
{});
+            var activityAttr = android_manifest_xml.find('application/activity').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(3);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).not.toBeDefined();
+        });
+        it ('should be able to handle absolute selectors with wildcards', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"overwrite\"]').getchildren();
+            xml_helpers.graftXMLOverwrite(android_manifest_xml, children, '/*/*/activity',
{});
+            var activityAttr = android_manifest_xml.find('application/activity').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(3);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).not.toBeDefined();
+        });
+        it ('should be able to handle xpath selectors', function() {
+            var children = plugin_xml.find('platform/edit-config[@mode=\"overwrite\"]').getchildren();
+            xml_helpers.graftXMLOverwrite(android_manifest_xml, children, 'application/activity[@android:name=\"ChildApp\"]',
{});
+            var activityAttr = android_manifest_xml.find('application/activity').attrib;
+            expect(Object.keys(activityAttr).length).toEqual(3);
+            expect(activityAttr['android:enabled']).toEqual('true');
+            expect(activityAttr['android:configChanges']).not.toBeDefined();
+        });
+    });
+
     describe('mergeXml', function () {
         var dstXml;
         beforeEach(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/ConfigChanges/ConfigChanges.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/ConfigChanges.js b/cordova-common/src/ConfigChanges/ConfigChanges.js
index a914fc8..a395c6d 100644
--- a/cordova-common/src/ConfigChanges/ConfigChanges.js
+++ b/cordova-common/src/ConfigChanges/ConfigChanges.js
@@ -36,9 +36,11 @@ var fs   = require('fs'),
     et   = require('elementtree'),
     semver = require('semver'),
     events = require('../events'),
-    ConfigKeeper = require('./ConfigKeeper');
+    ConfigKeeper = require('./ConfigKeeper'),
+    CordovaLogger = require('../CordovaLogger');
 
 var mungeutil = require('./munge-util');
+var xml_helpers = require('../util/xml-helpers');
 
 exports.PlatformMunger = PlatformMunger;
 
@@ -95,9 +97,10 @@ function remove_plugin_changes(pluginInfo, is_top_level) {
     var plugin_vars = is_top_level ?
         platform_config.installed_plugins[pluginInfo.id] :
         platform_config.dependent_plugins[pluginInfo.id];
+    var edit_config_changes = pluginInfo.getEditConfigs(self.platform);
 
     // get config munge, aka how did this plugin change various config files
-    var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars);
+    var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
     // global munge looks at all plugins' changes to config files
     var global_munge = platform_config.config_munge;
     var munge = mungeutil.decrement_munge(global_munge, config_munge);
@@ -125,12 +128,40 @@ function remove_plugin_changes(pluginInfo, is_top_level) {
 
 
 PlatformMunger.prototype.add_plugin_changes = add_plugin_changes;
-function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increment) {
+function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increment, plugin_force)
{
     var self = this;
     var platform_config = self.platformJson.root;
+    var edit_config_changes = pluginInfo.getEditConfigs(self.platform);
+    var config_munge;
 
-    // get config munge, aka how should this plugin change various config files
-    var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars);
+    if (!edit_config_changes || edit_config_changes.length === 0) {
+        // get config munge, aka how should this plugin change various config files
+        config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars);
+    }
+    else {
+        var isConflictingInfo = is_conflicting(edit_config_changes, platform_config.config_munge,
self, plugin_force);
+        if (plugin_force) {
+            CordovaLogger.get().log(CordovaLogger.WARN, '--force is used. edit-config will
overwrite conflicts if any. Conflicting plugins may not work as expected.');
+
+            // remove conflicting munges
+            var conflict_munge = mungeutil.decrement_munge(platform_config.config_munge,
isConflictingInfo.conflictingMunge);
+            for (var conflict_file in conflict_munge.files) {
+                self.apply_file_munge(conflict_file, conflict_munge.files[conflict_file],
/* remove = */ true);
+            }
+
+            // force add new munges
+            config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
+        }
+        else if(isConflictingInfo.conflictFound) {
+            throw new Error('There was a conflict trying to modify attributes with <edit-config>
in plugin ' + pluginInfo.id +
+            '. The conflicting plugin, ' + isConflictingInfo.conflictingPlugin + ', already
modified the same attributes. The conflict must be resolved before ' +
+            pluginInfo.id + ' can be added. You may use --force to add the plugin and overwrite
the conflicting attributes.');
+        }
+        else {
+            // no conflicts, will handle edit-config
+            config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
+        }
+    }
     // global munge looks at all plugins' changes to config files
 
     // TODO: The should_increment param is only used by cordova-cli and is going away soon.
@@ -186,13 +217,17 @@ function reapply_global_munge () {
 // generate_plugin_config_munge
 // Generate the munge object from plugin.xml + vars
 PlatformMunger.prototype.generate_plugin_config_munge = generate_plugin_config_munge;
-function generate_plugin_config_munge(pluginInfo, vars) {
+function generate_plugin_config_munge(pluginInfo, vars, edit_config_changes) {
     var self = this;
 
     vars = vars || {};
     var munge = { files: {} };
     var changes = pluginInfo.getConfigFiles(self.platform);
 
+    if(edit_config_changes) {
+        Array.prototype.push.apply(changes, edit_config_changes);
+    }
+
     // Demux 'package.appxmanifest' into relevant platform-specific appx manifests.
     // Only spend the cycles if there are version-specific plugin settings
     if (self.platform === 'windows' &&
@@ -291,12 +326,69 @@ function generate_plugin_config_munge(pluginInfo, vars) {
                 });
             }
             // 2. add into munge
-            mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count:
1, after: change.after });
+            if (change.mode) {
+                mungeutil.deep_add(munge, change.file, change.target, { xml: stringified,
count: 1, mode: change.mode, plugin: pluginInfo.id });
+            }
+            else {
+                mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified,
count: 1, after: change.after });
+            }
         });
     });
     return munge;
 }
 
+function is_conflicting(editchanges, config_munge, self, force) {
+    var files = config_munge.files;
+    var conflictFound = false;
+    var conflictingMunge = { files: {} };
+    var conflictingParent;
+    var conflictingPlugin;
+
+    editchanges.forEach(function(editchange) {
+        if (files[editchange.file]) {
+            var parents = files[editchange.file].parents;
+            var target = parents[editchange.target];
+
+            // Check if the edit target will resolve to an existing target
+            if (!target || target.length === 0) {
+                var file_xml = self.config_keeper.get(self.project_dir, self.platform, editchange.file).data;
+                var resolveEditTarget = xml_helpers.resolveParent(file_xml, editchange.target);
+                var resolveTarget;
+
+                if (resolveEditTarget) {
+                    for (var parent in parents) {
+                        resolveTarget = xml_helpers.resolveParent(file_xml, parent);
+                        if (resolveEditTarget === resolveTarget) {
+                            conflictingParent = parent;
+                            target = parents[parent];
+                            break;
+                        }
+                    }
+                }
+            }
+            else {
+                conflictingParent = editchange.target;
+            }
+
+            if (target.length !== 0) {
+                // conflict has been found, exit and throw an error
+                conflictFound = true;
+                if (!force) {
+                    // since there has been modifications to the attributes at this target,
+                    // the current plugin should not modify the attributes
+                    conflictingPlugin = target[0].plugin;
+                    return;
+                }
+
+                // need to find all conflicts when --force is used, track conflicting munges
+                mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent,
target[0]);
+            }
+        }
+    });
+
+    return {conflictFound: conflictFound, conflictingPlugin: conflictingPlugin, conflictingMunge:
conflictingMunge};
+}
+
 // Go over the prepare queue and apply the config munges for each plugin
 // that has been (un)installed.
 PlatformMunger.prototype.process = PlatformMunger_process;
@@ -313,7 +405,7 @@ function PlatformMunger_process(plugins_dir) {
     // Now handle installation
     platform_config.prepare_queue.installed.forEach(function(u) {
         var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin));
-        self.add_plugin_changes(pluginInfo, u.vars, u.topLevel, true);
+        self.add_plugin_changes(pluginInfo, u.vars, u.topLevel, true, u.force);
     });
 
     // Empty out installed/ uninstalled queues.

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/ConfigChanges/ConfigFile.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/ConfigFile.js b/cordova-common/src/ConfigChanges/ConfigFile.js
index 9c540b9..179d54d 100644
--- a/cordova-common/src/ConfigChanges/ConfigFile.js
+++ b/cordova-common/src/ConfigChanges/ConfigFile.js
@@ -103,7 +103,16 @@ ConfigFile.prototype.graft_child = function ConfigFile_graft_child(selector,
xml
     var result;
     if (self.type === 'xml') {
         var xml_to_graft = [modules.et.XML(xml_child.xml)];
-        result = modules.xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after);
+        switch (xml_child.mode) {
+            case 'merge':
+                result = modules.xml_helpers.graftXMLMerge(self.data, xml_to_graft, selector,
xml_child);
+                break;
+            case 'overwrite':
+                result = modules.xml_helpers.graftXMLOverwrite(self.data, xml_to_graft, selector,
xml_child);
+                break;
+            default:
+                result = modules.xml_helpers.graftXML(self.data, xml_to_graft, selector,
xml_child.after);
+        }
         if ( !result) {
             throw new Error('Unable to graft xml at selector "' + selector + '" from "' +
filepath + '" during config install');
         }
@@ -123,7 +132,14 @@ ConfigFile.prototype.prune_child = function ConfigFile_prune_child(selector,
xml
     var result;
     if (self.type === 'xml') {
         var xml_to_graft = [modules.et.XML(xml_child.xml)];
-        result = modules.xml_helpers.pruneXML(self.data, xml_to_graft, selector);
+        switch (xml_child.mode) {
+            case 'merge':
+            case 'overwrite':
+                result = modules.xml_helpers.pruneXMLRestore(self.data, selector, xml_child);
+                break;
+            default:
+                result = modules.xml_helpers.pruneXML(self.data, xml_to_graft, selector);
+        }
     } else {
         // plist file
         result = modules.plist_helpers.prunePLIST(self.data, xml_child.xml, selector);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/ConfigChanges/munge-util.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/munge-util.js b/cordova-common/src/ConfigChanges/munge-util.js
index 307b3c1..0149bab 100644
--- a/cordova-common/src/ConfigChanges/munge-util.js
+++ b/cordova-common/src/ConfigChanges/munge-util.js
@@ -52,6 +52,9 @@ exports.deep_remove = function deep_remove(obj, keys /* or key1, key2 ....
*/ )
             return element.xml == k.xml;
         });
         if (found) {
+            if (parentArray[index].oldAttrib) {
+                k.oldAttrib = _.extend({}, parentArray[index].oldAttrib);
+            }
             found.count -= k.count;
             if (found.count > 0) {
                 return false;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/PlatformJson.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/PlatformJson.js b/cordova-common/src/PlatformJson.js
index 4e2b287..ab94b5f 100644
--- a/cordova-common/src/PlatformJson.js
+++ b/cordova-common/src/PlatformJson.js
@@ -162,8 +162,8 @@ PlatformJson.prototype.removePluginMetadata = function (pluginInfo) {
     return this;
 };
 
-PlatformJson.prototype.addInstalledPluginToPrepareQueue = function(pluginDirName, vars, is_top_level)
{
-    this.root.prepare_queue.installed.push({'plugin':pluginDirName, 'vars':vars, 'topLevel':is_top_level});
+PlatformJson.prototype.addInstalledPluginToPrepareQueue = function(pluginDirName, vars, is_top_level,
force) {
+    this.root.prepare_queue.installed.push({'plugin':pluginDirName, 'vars':vars, 'topLevel':is_top_level,
'force':force});
 };
 
 PlatformJson.prototype.addUninstalledPluginToPrepareQueue = function(pluginId, is_top_level)
{
@@ -276,4 +276,3 @@ function ModuleMetadata (pluginId, jsModule) {
 }
 
 module.exports = PlatformJson;
-

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/PluginInfo/PluginInfo.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/PluginInfo/PluginInfo.js b/cordova-common/src/PluginInfo/PluginInfo.js
index 77199ec..7dd8cc3 100644
--- a/cordova-common/src/PluginInfo/PluginInfo.js
+++ b/cordova-common/src/PluginInfo/PluginInfo.js
@@ -146,6 +146,22 @@ function PluginInfo(dirname) {
         return configFile;
     }
 
+    self.getEditConfigs = getEditConfigs;
+    function getEditConfigs(platform) {
+        var editConfigs = _getTags(self._et, 'edit-config', platform, _parseEditConfigs);
+        return editConfigs;
+    }
+
+    function _parseEditConfigs(tag) {
+        var editConfig =
+        { file : tag.attrib['file']
+        , target : tag.attrib['target']
+        , mode : tag.attrib['mode']
+        , xmls : tag.getchildren()
+        };
+        return editConfig;
+    }
+
     // <info> tags, both global and within a <platform>
     // TODO (kamrik): Do we ever use <info> under <platform>? Example wanted.
     self.getInfo = getInfo;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/PluginManager.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/PluginManager.js b/cordova-common/src/PluginManager.js
index deeb92f..0bcaf59 100644
--- a/cordova-common/src/PluginManager.js
+++ b/cordova-common/src/PluginManager.js
@@ -123,7 +123,7 @@ PluginManager.prototype.doOperation = function (operation, plugin, options)
{
         if (operation === PluginManager.INSTALL) {
             // Ignore passed `is_top_level` option since platform itself doesn't know
             // anything about managing dependencies - it's responsibility of caller.
-            self.munger.add_plugin_changes(plugin, options.variables, /*is_top_level=*/true,
/*should_increment=*/true);
+            self.munger.add_plugin_changes(plugin, options.variables, /*is_top_level=*/true,
/*should_increment=*/true, options.force);
             self.munger.platformJson.addPluginMetadata(plugin);
         } else {
             self.munger.remove_plugin_changes(plugin, /*is_top_level=*/true);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/5fb8dffb/cordova-common/src/util/xml-helpers.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/util/xml-helpers.js b/cordova-common/src/util/xml-helpers.js
index 6366af9..f16eaaf 100644
--- a/cordova-common/src/util/xml-helpers.js
+++ b/cordova-common/src/util/xml-helpers.js
@@ -29,6 +29,9 @@ var fs = require('fs')
   , et = require('elementtree')
   ;
 
+  var ROOT = /^\/([^\/]*)/,
+      ABSOLUTE = /^\/([^\/]*)\/(.*)/;
+
 module.exports = {
     // compare two et.XML nodes, see if they match
     // compares tagName, text, attributes and children (recursively)
@@ -68,7 +71,7 @@ module.exports = {
 
     // adds node to doc at selector, creating parent if it doesn't exist
     graftXML: function(doc, nodes, selector, after) {
-        var parent = resolveParent(doc, selector);
+        var parent = module.exports.resolveParent(doc, selector);
         if (!parent) {
             //Try to create the parent recursively if necessary
             try {
@@ -79,7 +82,7 @@ module.exports = {
             } catch (e) {
                 return false;
             }
-            parent = resolveParent(doc, selector);
+            parent = module.exports.resolveParent(doc, selector);
             if (!parent) return false;
         }
 
@@ -97,9 +100,54 @@ module.exports = {
         return true;
     },
 
+    // adds new attributes to doc at selector
+    // Will only merge if attribute has not been modified already or --force is used
+    graftXMLMerge: function(doc, nodes, selector, xml) {
+        var target = module.exports.resolveParent(doc, selector);
+        if (!target) return false;
+
+        // saves the attributes of the original xml before making changes
+        xml.oldAttrib = _.extend({}, target.attrib);
+
+        nodes.forEach(function (node) {
+            var attributes = node.attrib;
+            for (var attribute in attributes) {
+                target.attrib[attribute] = node.attrib[attribute];
+            }
+        });
+
+        return true;
+    },
+
+    // overwrite all attributes to doc at selector with new attributes
+    // Will only overwrite if attribute has not been modified already or --force is used
+    graftXMLOverwrite: function(doc, nodes, selector, xml) {
+        var target = module.exports.resolveParent(doc, selector);
+        if (!target) return false;
+
+        // saves the attributes of the original xml before making changes
+        xml.oldAttrib = _.extend({}, target.attrib);
+
+        // remove old attributes from target
+        var targetAttributes = target.attrib;
+        for (var targetAttribute in targetAttributes) {
+            delete targetAttributes[targetAttribute];
+        }
+
+        // add new attributes to target
+        nodes.forEach(function (node) {
+            var attributes = node.attrib;
+            for (var attribute in attributes) {
+                target.attrib[attribute] = node.attrib[attribute];
+            }
+        });
+
+        return true;
+    },
+
     // removes node from doc at selector
     pruneXML: function(doc, nodes, selector) {
-        var parent = resolveParent(doc, selector);
+        var parent = module.exports.resolveParent(doc, selector);
         if (!parent) return false;
 
         nodes.forEach(function (node) {
@@ -114,6 +162,19 @@ module.exports = {
         return true;
     },
 
+    // restores attributes from doc at selector
+    pruneXMLRestore: function(doc, selector, xml) {
+        var target = module.exports.resolveParent(doc, selector);
+        if (!target) return false;
+
+        if (xml.oldAttrib) {
+            target.attrib = _.extend({}, xml.oldAttrib);
+        }
+
+        return true;
+    },
+
+
     parseElementtreeSync: function (filename) {
         var contents = fs.readFileSync(filename, 'utf-8');
         if(contents) {
@@ -121,6 +182,30 @@ module.exports = {
             contents = contents.substring(contents.indexOf('<'));
         }
         return new et.ElementTree(et.XML(contents));
+    },
+
+    resolveParent: function (doc, selector) {
+        var parent, tagName, subSelector;
+
+        // handle absolute selector (which elementtree doesn't like)
+        if (ROOT.test(selector)) {
+            tagName = selector.match(ROOT)[1];
+            // test for wildcard "any-tag" root selector
+            if (tagName == '*' || tagName === doc._root.tag) {
+                parent = doc._root;
+
+                // could be an absolute path, but not selecting the root
+                if (ABSOLUTE.test(selector)) {
+                    subSelector = selector.match(ABSOLUTE)[2];
+                    parent = parent.find(subSelector);
+                }
+            } else {
+                return false;
+            }
+        } else {
+            parent = doc.find(selector);
+        }
+        return parent;
     }
 };
 
@@ -152,33 +237,6 @@ function uniqueChild(node, parent) {
     }
 }
 
-var ROOT = /^\/([^\/]*)/,
-    ABSOLUTE = /^\/([^\/]*)\/(.*)/;
-
-function resolveParent(doc, selector) {
-    var parent, tagName, subSelector;
-
-    // handle absolute selector (which elementtree doesn't like)
-    if (ROOT.test(selector)) {
-        tagName = selector.match(ROOT)[1];
-        // test for wildcard "any-tag" root selector
-        if (tagName == '*' || tagName === doc._root.tag) {
-            parent = doc._root;
-
-            // could be an absolute path, but not selecting the root
-            if (ABSOLUTE.test(selector)) {
-                subSelector = selector.match(ABSOLUTE)[2];
-                parent = parent.find(subSelector);
-            }
-        } else {
-            return false;
-        }
-    } else {
-        parent = doc.find(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
@@ -257,19 +315,19 @@ function mergeXml(src, dest, platform, clobber) {
             dest.append(destChild);
         }
     }
-    
+
     function removeDuplicatePreferences(xml) {
         // reduce preference tags to a hashtable to remove dupes
         var prefHash = xml.findall('preference[@name][@value]').reduce(function(previousValue,
currentValue) {
             previousValue[ currentValue.attrib.name ] = currentValue.attrib.value;
             return previousValue;
         }, {});
-        
+
         // remove all preferences
         xml.findall('preference[@name][@value]').forEach(function(pref) {
             xml.remove(pref);
         });
-        
+
         // write new preferences
         Object.keys(prefHash).forEach(function(key, index) {
             var element = et.SubElement(xml, 'preference');


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


Mime
View raw message