CB-7142 Add <variable> to <feature> for "plugin restore" command
github: close #63 Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/6050e93e Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/6050e93e Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/6050e93e Branch: refs/heads/master Commit: 6050e93ece9dc0521f1e70274f769fe7644f80c9 Parents: 3de053c Author: motorro <moto...@gmail.com> Authored: Wed Jul 16 05:43:36 2014 +0400 Committer: Anis Kadri <a...@apache.org> Committed: Fri Sep 5 11:12:18 2014 -0700 ---------------------------------------------------------------------- cordova-lib/spec-cordova/ConfigParser.spec.js | 158 +++++++++++++++++++ cordova-lib/spec-cordova/test-config.xml | 24 ++- cordova-lib/src/configparser/ConfigParser.js | 96 +++++++++++ .../src/configparser/ConfigParser.spec.js | 99 ------------ cordova-lib/src/cordova/platform.js | 17 +- cordova-lib/src/cordova/prepare.js | 2 +- cordova-lib/src/cordova/restore.js | 60 +++---- 7 files changed, 325 insertions(+), 131 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/spec-cordova/ConfigParser.spec.js ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-cordova/ConfigParser.spec.js b/cordova-lib/spec-cordova/ConfigParser.spec.js new file mode 100644 index 0000000..9c8c8e6 --- /dev/null +++ b/cordova-lib/spec-cordova/ConfigParser.spec.js @@ -0,0 +1,158 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +*/ +var path = require('path'), + fs = require('fs'), + ConfigParser = require('../src/configparser/ConfigParser'), + xml = path.join(__dirname, 'test-config.xml'), + xml_contents = fs.readFileSync(xml, 'utf-8'); + +describe('config.xml parser', function () { + var readFile; + beforeEach(function() { + readFile = spyOn(fs, 'readFileSync').andReturn(xml_contents); + }); + + it('should create an instance based on an xml file', function() { + var cfg; + expect(function () { + cfg = new ConfigParser(xml); + }).not.toThrow(); + expect(cfg).toBeDefined(); + expect(cfg.doc).toBeDefined(); + }); + + describe('methods', function() { + var cfg; + beforeEach(function() { + cfg = new ConfigParser(xml); + }); + + describe('package name / id', function() { + it('should get the (default) packagename', function() { + expect(cfg.packageName()).toEqual('io.cordova.hellocordova'); + }); + it('should allow setting the packagename', function() { + cfg.setPackageName('this.is.bat.country'); + expect(cfg.packageName()).toEqual('this.is.bat.country'); + }); + }); + + describe('version', function() { + it('should get the version', function() { + expect(cfg.version()).toEqual('0.0.1'); + }); + it('should allow setting the version', function() { + cfg.setVersion('2.0.1'); + expect(cfg.version()).toEqual('2.0.1'); + }); + }); + + describe('app name', function() { + it('should get the (default) app name', function() { + expect(cfg.name()).toEqual('Hello Cordova'); + }); + it('should allow setting the app name', function() { + cfg.setName('this.is.bat.country'); + expect(cfg.name()).toEqual('this.is.bat.country'); + }); + }); + describe('preference', function() { + it('should get value of existing preference', function() { + expect(cfg.getPreference('fullscreen')).toEqual('true'); + }); + it('should get undefined as non existing preference', function() { + expect(cfg.getPreference('zimzooo!')).toEqual(undefined); + }); + }); + describe('feature',function(){ + it('should read feature id list', function() { + var expectedList = [ + "org.apache.cordova.featurewithvars", + "org.apache.cordova.featurewithurl", + "org.apache.cordova.featurewithversion", + "org.apache.cordova.featurewithurlandversion", + "org.apache.cordova.justafeature" + ]; + var list = cfg.getFeatureIdList(); + expect(list.length).toEqual(expectedList.length); + expectedList.forEach(function(feature){ + expect(list).toContain(feature); + }); + }); + it('should read feature given id', function(){ + var feature = cfg.getFeature("org.apache.cordova.justafeature"); + expect(feature).toBeDefined(); + expect(feature.name).toEqual("A simple feature"); + expect(feature.id).toEqual("org.apache.cordova.justafeature"); + expect(feature.params).toBeDefined(); + expect(feature.params.id).toBeDefined(); + expect(feature.params.id).toEqual("org.apache.cordova.justafeature"); + }); + it('should not read feature given undefined id', function(){ + var feature = cfg.getFeature("org.apache.cordova.undefinedfeature"); + expect(feature).not.toBeDefined(); + }); + it('should read feature with url and set \'url\' param', function(){ + var feature = cfg.getFeature("org.apache.cordova.featurewithurl"); + expect(feature.url).toEqual("http://cordova.apache.org/featurewithurl"); + expect(feature.params).toBeDefined(); + expect(feature.params.url).toBeDefined(); + expect(feature.params.url).toEqual("http://cordova.apache.org/featurewithurl"); + }); + it('should read feature with version and set \'version\' param', function(){ + var feature = cfg.getFeature("org.apache.cordova.featurewithversion"); + expect(feature.version).toEqual("1.1.1"); + expect(feature.params).toBeDefined(); + expect(feature.params.version).toBeDefined(); + expect(feature.params.version).toEqual("1.1.1"); + }); + it('should read feature variables', function () { + var feature = cfg.getFeature("org.apache.cordova.featurewithvars"); + expect(feature.variables).toBeDefined(); + expect(feature.variables.var).toBeDefined(); + expect(feature.variables.var).toEqual("varvalue"); + }); + it('should allow adding a new feature', function(){ + cfg.addFeature('myfeature'); + var features = cfg.doc.findall('feature'); + var featureNames = features.map(function(feature){ + return feature.attrib.name; + }); + expect(featureNames).toContain('myfeature'); + }); + it('should allow adding features with params', function(){ + cfg.addFeature('afeature', JSON.parse('[{"name":"paraname", "value":"paravalue"}]')); + var features = cfg.doc.findall('feature'); + var feature = (function(){ + var i = features.length; + var f; + while (--i >= 0) { + f = features[i]; + if ('afeature' === f.attrib.name) return f; + } + return undefined; + })(); + expect(feature).toBeDefined(); + var params = feature.findall('param'); + expect(params[0].attrib.name).toEqual('paraname'); + expect(params[0].attrib.value).toEqual('paravalue'); + }); + }); + }); +}); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/spec-cordova/test-config.xml ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-cordova/test-config.xml b/cordova-lib/spec-cordova/test-config.xml index 5e01e48..3308ae0 100644 --- a/cordova-lib/spec-cordova/test-config.xml +++ b/cordova-lib/spec-cordova/test-config.xml @@ -22,5 +22,27 @@ <icon id="logo" src="logo.png" width="255" height="255" /> <platform name="android"> <icon src="logo-android.png" width="255" height="255" density="mdpi" /> - </platform> + </platform> + + <!-- Features --> + <feature name="A feature with preference"> + <param name="id" value="org.apache.cordova.featurewithvars"/> + <variable name="var" value="varvalue"/> + </feature> + <feature name="A feature with url"> + <param name="id" value="org.apache.cordova.featurewithurl" /> + <param name="url" value="http://cordova.apache.org/featurewithurl" /> + </feature> + <feature name="A feature with version"> + <param name="id" value="org.apache.cordova.featurewithversion" /> + <param name="version" value="1.1.1" /> + </feature> + <feature name="A feature with url and version"> + <param name="id" value="org.apache.cordova.featurewithurlandversion" /> + <param name="version" value="1.1.1" /> + <param name="url" value="http://cordova.apache.org/featurewithurlandversion" /> + </feature> + <feature name="A simple feature"> + <param name="id" value="org.apache.cordova.justafeature" /> + </feature> </widget> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/src/configparser/ConfigParser.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/configparser/ConfigParser.js b/cordova-lib/src/configparser/ConfigParser.js index 84de2a6..75f798b 100644 --- a/cordova-lib/src/configparser/ConfigParser.js +++ b/cordova-lib/src/configparser/ConfigParser.js @@ -27,6 +27,15 @@ var et = require('elementtree'), CordovaError = require('../CordovaError'), fs = require('fs'); +/** + * Array of 'feature' params that are set as properties + * @type {string[]} + */ +var FEATURE_SPECIAL_PARAMS = [ + 'id', + 'url', + 'version' +]; /** Wraps a config.xml file */ function ConfigParser(path) { @@ -217,6 +226,93 @@ ConfigParser.prototype = { }, /** + * Returns a list of features (IDs) + * @return {string[]} Array of feature IDs + */ + getFeatureIdList: function () { + var features = this.doc.findall('feature'), + feature, idTag, id, + result = []; + + // Check for valid features that have IDs set + for (var i = 0, l = features.length; i < l; ++i) { + feature = features[i]; + idTag = feature.find('./param[@name="id"]'); + if (null === idTag) { + // Invalid feature + continue; + } + id = idTag.attrib.value; + if (!!id) { + // Has id and id is non-empty + result.push(id); + } + } + + return result; + }, + + /** + * Gets feature info + * @param {string} id Feature id + * @returns {Feature} Feature object + */ + getFeature: function(id) { + if (!id) { + return undefined; + } + var feature = this.doc.find('./feature/param[@name="id"][@value="' + id + '"]/..'); + if (null === feature) { + return undefined; + } + + var result = {}; + result.id = id; + result.name = feature.attrib.name; + + // Iterate params and fill-in 'params' structure + // For special cases like 'id', 'url, 'version' - copy to the main space + result.params = processChildren ( + 'param', + function(name, value) { + if (FEATURE_SPECIAL_PARAMS.indexOf(name) >= 0) { + result[name] = value; + } + } + ); + + // Iterate preferences + result.variables = processChildren('variable'); + + return result; + + /** + * Processes a set of children + * having a pair of 'name' and 'value' attributes + * filling in 'output' object + * @param {string} xPath Search expression + * @param {function} [specialProcessing] Performs some additional actions on each valid element + * @return {object} A transformed object + */ + function processChildren (xPath, specialProcessing) { + var result = {}; + var needsProcessing = 'function' === typeof specialProcessing; + var nodes = feature.findall(xPath); + nodes.forEach(function(param){ + var name = param.attrib.name; + var value = param.attrib.value; + if (name) { + result[name] = value; + if (needsProcessing) { + specialProcessing(name, value); + } + } + }); + return result; + } + }, + + /** *This does not check for duplicate feature entries */ addFeature: function (name, params){ http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/src/configparser/ConfigParser.spec.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/configparser/ConfigParser.spec.js b/cordova-lib/src/configparser/ConfigParser.spec.js deleted file mode 100644 index a1ba102..0000000 --- a/cordova-lib/src/configparser/ConfigParser.spec.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you 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. -*/ -var path = require('path'), - fs = require('fs'), - ConfigParser = require('../src/cordova/ConfigParser'), - xml = path.join(__dirname, 'test-config.xml'), - xml_contents = fs.readFileSync(xml, 'utf-8'); - -describe('config.xml parser', function () { - var readFile; - beforeEach(function() { - readFile = spyOn(fs, 'readFileSync').andReturn(xml_contents); - }); - - it('should create an instance based on an xml file', function() { - var cfg; - expect(function () { - cfg = new ConfigParser(xml); - }).not.toThrow(); - expect(cfg).toBeDefined(); - expect(cfg.doc).toBeDefined(); - }); - - describe('methods', function() { - var cfg; - beforeEach(function() { - cfg = new ConfigParser(xml); - }); - - describe('package name / id', function() { - it('should get the (default) packagename', function() { - expect(cfg.packageName()).toEqual('io.cordova.hellocordova'); - }); - it('should allow setting the packagename', function() { - cfg.setPackageName('this.is.bat.country'); - expect(cfg.packageName()).toEqual('this.is.bat.country'); - }); - }); - - describe('version', function() { - it('should get the version', function() { - expect(cfg.version()).toEqual('0.0.1'); - }); - it('should allow setting the version', function() { - cfg.setVersion('2.0.1'); - expect(cfg.version()).toEqual('2.0.1'); - }); - }); - - describe('app name', function() { - it('should get the (default) app name', function() { - expect(cfg.name()).toEqual('Hello Cordova'); - }); - it('should allow setting the app name', function() { - cfg.setName('this.is.bat.country'); - expect(cfg.name()).toEqual('this.is.bat.country'); - }); - }); - describe('preference', function() { - it('should get value of existing preference', function() { - expect(cfg.getPreference('fullscreen')).toEqual('true'); - }); - it('should get undefined as non existing preference', function() { - expect(cfg.getPreference('zimzooo!')).toEqual(undefined); - }); - }); - describe('feature',function(){ - it('should allow adding a new feature', function(){ - cfg.addFeature('myfeature'); - var features = cfg.doc.findall('feature'); - expect(features[0].attrib.name).toEqual('myfeature'); - }); - it('should allow adding features with params', function(){ - cfg.addFeature('afeature', JSON.parse('[{"name":"paraname", "value":"paravalue"}]')); - var features = cfg.doc.findall('feature'); - expect(features[0].attrib.name).toEqual('afeature'); - var params = features[0].findall('param'); - expect(params[0].attrib.name).toEqual('paraname'); - expect(params[0].attrib.value).toEqual('paravalue'); - }); - }); - }); -}); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/src/cordova/platform.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js index 2cb8c2e..7a01783 100644 --- a/cordova-lib/src/cordova/platform.js +++ b/cordova-lib/src/cordova/platform.js @@ -454,7 +454,22 @@ function call_into_create(target, projectRoot, cfg, libDir, template_dir, opts) return plugins.reduce(function(soFar, plugin) { return soFar.then(function() { events.emit('verbose', 'Installing plugin "' + plugin + '" following successful platform add of ' + target); - return plugman.raw.install(target, output, path.basename(plugin), plugins_dir); + plugin = path.basename(plugin); + var options = (function(){ + // Get plugin preferences from config features if have any + // Pass them as cli_variables to plugman + var feature = cfg.getFeature(plugin); + var variables = feature && feature.variables; + if (!!variables) { + events.emit('verbose', 'Found variables for "' + plugin + '". Processing as cli_variables.'); + return { + cli_variables: variables + }; + } + return null; + })(); + + return plugman.raw.install(target, output, plugin, plugins_dir, options); }); }, Q()); }); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/src/cordova/prepare.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/cordova/prepare.js b/cordova-lib/src/cordova/prepare.js index c69a4d7..09be122 100644 --- a/cordova-lib/src/cordova/prepare.js +++ b/cordova-lib/src/cordova/prepare.js @@ -126,7 +126,7 @@ function prepare(options) { }); } -var BLACKLIST = ['platform']; +var BLACKLIST = ['platform', 'feature']; var SINGLETONS = ['content', 'author']; function mergeXml(src, dest, platform, clobber) { // Do nothing for blacklisted tags. http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6050e93e/cordova-lib/src/cordova/restore.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/cordova/restore.js b/cordova-lib/src/cordova/restore.js index ffd1edc..6e4d262 100644 --- a/cordova-lib/src/cordova/restore.js +++ b/cordova-lib/src/cordova/restore.js @@ -60,39 +60,41 @@ function installPlatformsFromConfigXML(cfg){ //returns a Promise function installPluginsFromConfigXML(cfg) { //Install plugins that are listed on config.xml - var pluginsFromConfig = []; var projectRoot = cordova_util.cdProjectRoot(); var plugins_dir = path.join(projectRoot, 'plugins'); - var features = cfg.doc.findall('feature'); - features.forEach(function(feature){ - var params = feature.findall('param'); - var pluginId = ''; - var pluginVersion = ''; - for (var i = 0; i < params.length; i++) { - if (params[i].attrib.name === 'id') { - pluginId = params[i].attrib.value; - } - if (params[i].attrib.name === 'version') { - pluginVersion = params[i].attrib.value; - } + // Get all configured plugins + var features = cfg.getFeatureIdList(); + if (0 === features.length) { + return Q.all('No config.xml plugins to install'); + } + + return features.reduce(function(soFar, featureId) { + + var pluginPath = path.join(plugins_dir, featureId); + if (fs.existsSync(pluginPath)) { + // Plugin already exists + return soFar; } - var pluginPath = path.join(plugins_dir,pluginId); - // contents of the plugins folder takes precedence hence - // we ignore if the correct version is installed or not. - if (pluginId !== '' && !fs.existsSync(pluginPath)) { - if ( pluginVersion !== '') { - pluginId = pluginId + '@' + pluginVersion; + + return soFar.then(function() { + events.emit('log', 'Discovered ' + featureId + ' in config.xml. Installing to the project'); + + var feature = cfg.getFeature(featureId); + + // Install from given URL if defined or using a plugin id + var installFrom = feature.url; + if (!installFrom) { + installFrom = feature.id; + if (!!feature.version) { + installFrom += ('@' + feature.version); + } } - events.emit('log', 'Discovered ' + pluginId + ' in config.xml. Installing to the project'); - pluginsFromConfig.push(pluginId); - } - }); - //Use cli instead of plugman directly ensuring all the hooks - // to get fired. - if (pluginsFromConfig.length >0) { - return plugin('add', pluginsFromConfig); - } - return Q.all('No config.xml plugins to install'); + // Add feature preferences as CLI variables if have any + var options = 'undefined' !== typeof feature.variables ? {cli_variables: feature.variables} : null; + + return plugin('add', installFrom, options); + }); + }, Q()); }