http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ActionStack.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ActionStack.js b/node_modules/cordova-common/src/ActionStack.js new file mode 100644 index 0000000..5ef6f84 --- /dev/null +++ b/node_modules/cordova-common/src/ActionStack.js @@ -0,0 +1,85 @@ +/** + 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. +*/ + +/* jshint quotmark:false */ + +var events = require('./events'), + Q = require('q'); + +function ActionStack() { + this.stack = []; + this.completed = []; +} + +ActionStack.prototype = { + createAction:function(handler, action_params, reverter, revert_params) { + return { + handler:{ + run:handler, + params:action_params + }, + reverter:{ + run:reverter, + params:revert_params + } + }; + }, + push:function(tx) { + this.stack.push(tx); + }, + // Returns a promise. + process:function(platform) { + events.emit('verbose', 'Beginning processing of action stack for ' + platform + ' project...'); + + while (this.stack.length) { + var action = this.stack.shift(); + var handler = action.handler.run; + var action_params = action.handler.params; + + try { + handler.apply(null, action_params); + } catch(e) { + events.emit('warn', 'Error during processing of action! Attempting to revert...'); + this.stack.unshift(action); + var issue = 'Uh oh!\n'; + // revert completed tasks + while(this.completed.length) { + var undo = this.completed.shift(); + var revert = undo.reverter.run; + var revert_params = undo.reverter.params; + + try { + revert.apply(null, revert_params); + } catch(err) { + events.emit('warn', 'Error during reversion of action! We probably really messed up your project now, sorry! D:'); + issue += 'A reversion action failed: ' + err.message + '\n'; + } + } + e.message = issue + e.message; + return Q.reject(e); + } + this.completed.push(action); + } + events.emit('verbose', 'Action stack processing complete.'); + + return Q(); + } +}; + +module.exports = ActionStack;
http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js b/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js new file mode 100644 index 0000000..a914fc8 --- /dev/null +++ b/node_modules/cordova-common/src/ConfigChanges/ConfigChanges.js @@ -0,0 +1,323 @@ +/* + * + * 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. + * +*/ + +/* + * This module deals with shared configuration / dependency "stuff". That is: + * - XML configuration files such as config.xml, AndroidManifest.xml or WMAppManifest.xml. + * - plist files in iOS + * Essentially, any type of shared resources that we need to handle with awareness + * of how potentially multiple plugins depend on a single shared resource, should be + * handled in this module. + * + * The implementation uses an object as a hash table, with "leaves" of the table tracking + * reference counts. + */ + +/* jshint sub:true */ + +var fs = require('fs'), + path = require('path'), + et = require('elementtree'), + semver = require('semver'), + events = require('../events'), + ConfigKeeper = require('./ConfigKeeper'); + +var mungeutil = require('./munge-util'); + +exports.PlatformMunger = PlatformMunger; + +exports.process = function(plugins_dir, project_dir, platform, platformJson, pluginInfoProvider) { + var munger = new PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider); + munger.process(plugins_dir); + munger.save_all(); +}; + +/****************************************************************************** +* PlatformMunger class +* +* Can deal with config file of a single project. +* Parsed config files are cached in a ConfigKeeper object. +******************************************************************************/ +function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) { + this.platform = platform; + this.project_dir = project_dir; + this.config_keeper = new ConfigKeeper(project_dir); + this.platformJson = platformJson; + this.pluginInfoProvider = pluginInfoProvider; +} + +// Write out all unsaved files. +PlatformMunger.prototype.save_all = PlatformMunger_save_all; +function PlatformMunger_save_all() { + this.config_keeper.save_all(); + this.platformJson.save(); +} + +// Apply a munge object to a single config file. +// The remove parameter tells whether to add the change or remove it. +PlatformMunger.prototype.apply_file_munge = PlatformMunger_apply_file_munge; +function PlatformMunger_apply_file_munge(file, munge, remove) { + var self = this; + + for (var selector in munge.parents) { + for (var 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, munge.parents[selector][xml_child]); + else config_file.graft_child(selector, munge.parents[selector][xml_child]); + } + } + } +} + + +PlatformMunger.prototype.remove_plugin_changes = remove_plugin_changes; +function remove_plugin_changes(pluginInfo, is_top_level) { + var self = this; + var platform_config = self.platformJson.root; + var plugin_vars = is_top_level ? + platform_config.installed_plugins[pluginInfo.id] : + platform_config.dependent_plugins[pluginInfo.id]; + + // get config munge, aka how did this plugin change various config files + var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars); + // 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); + + for (var file in munge.files) { + // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins. + if (self.platform == 'windows' && file == 'package.appxmanifest' && + !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) { + // New windows template separate manifest files for Windows10, Windows8.1 and WP8.1 + var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows10.appxmanifest']; + /* jshint loopfunc:true */ + substs.forEach(function(subst) { + events.emit('verbose', 'Applying munge to ' + subst); + self.apply_file_munge(subst, munge.files[file], true); + }); + /* jshint loopfunc:false */ + } + self.apply_file_munge(file, munge.files[file], /* remove = */ true); + } + + // Remove from installed_plugins + self.platformJson.removePlugin(pluginInfo.id, is_top_level); + return self; +} + + +PlatformMunger.prototype.add_plugin_changes = add_plugin_changes; +function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increment) { + var self = this; + var platform_config = self.platformJson.root; + + // get config munge, aka how should this plugin change various config files + var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars); + // 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. + // If should_increment is set to false, avoid modifying the global_munge (use clone) + // and apply the entire config_munge because it's already a proper subset of the global_munge. + var munge, global_munge; + if (should_increment) { + global_munge = platform_config.config_munge; + munge = mungeutil.increment_munge(global_munge, config_munge); + } else { + global_munge = mungeutil.clone_munge(platform_config.config_munge); + munge = config_munge; + } + + for (var file in munge.files) { + // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins. + if (self.platform == 'windows' && file == 'package.appxmanifest' && + !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) { + var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows10.appxmanifest']; + /* jshint loopfunc:true */ + substs.forEach(function(subst) { + events.emit('verbose', 'Applying munge to ' + subst); + self.apply_file_munge(subst, munge.files[file]); + }); + /* jshint loopfunc:false */ + } + self.apply_file_munge(file, munge.files[file]); + } + + // Move to installed/dependent_plugins + self.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level); + return self; +} + + +// Load the global munge from platform json and apply all of it. +// Used by cordova prepare to re-generate some config file from platform +// defaults and the global munge. +PlatformMunger.prototype.reapply_global_munge = reapply_global_munge ; +function reapply_global_munge () { + var self = this; + + var platform_config = self.platformJson.root; + var global_munge = platform_config.config_munge; + for (var file in global_munge.files) { + self.apply_file_munge(file, global_munge.files[file]); + } + + return self; +} + + +// 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) { + var self = this; + + vars = vars || {}; + var munge = { files: {} }; + var changes = pluginInfo.getConfigFiles(self.platform); + + // Demux 'package.appxmanifest' into relevant platform-specific appx manifests. + // Only spend the cycles if there are version-specific plugin settings + if (self.platform === 'windows' && + changes.some(function(change) { + return ((typeof change.versions !== 'undefined') || + (typeof change.deviceTarget !== 'undefined')); + })) + { + var manifests = { + 'windows': { + '8.1.0': 'package.windows.appxmanifest', + '10.0.0': 'package.windows10.appxmanifest' + }, + 'phone': { + '8.1.0': 'package.phone.appxmanifest', + '10.0.0': 'package.windows10.appxmanifest' + }, + 'all': { + '8.1.0': ['package.windows.appxmanifest', 'package.phone.appxmanifest'], + '10.0.0': 'package.windows10.appxmanifest' + } + }; + + var oldChanges = changes; + changes = []; + + oldChanges.forEach(function(change, changeIndex) { + // Only support semver/device-target demux for package.appxmanifest + // Pass through in case something downstream wants to use it + if (change.target !== 'package.appxmanifest') { + changes.push(change); + return; + } + + var hasVersion = (typeof change.versions !== 'undefined'); + var hasTargets = (typeof change.deviceTarget !== 'undefined'); + + // No semver/device-target for this config-file, pass it through + if (!(hasVersion || hasTargets)) { + changes.push(change); + return; + } + + var targetDeviceSet = hasTargets ? change.deviceTarget : 'all'; + if (['windows', 'phone', 'all'].indexOf(targetDeviceSet) === -1) { + // target-device couldn't be resolved, fix it up here to a valid value + targetDeviceSet = 'all'; + } + var knownWindowsVersionsForTargetDeviceSet = Object.keys(manifests[targetDeviceSet]); + + // at this point, 'change' targets package.appxmanifest and has a version attribute + knownWindowsVersionsForTargetDeviceSet.forEach(function(winver) { + // This is a local function that creates the new replacement representing the + // mutation. Used to save code further down. + var createReplacement = function(manifestFile, originalChange) { + var replacement = { + target: manifestFile, + parent: originalChange.parent, + after: originalChange.after, + xmls: originalChange.xmls, + versions: originalChange.versions, + deviceTarget: originalChange.deviceTarget + }; + return replacement; + }; + + // version doesn't satisfy, so skip + if (hasVersion && !semver.satisfies(winver, change.versions)) { + return; + } + + var versionSpecificManifests = manifests[targetDeviceSet][winver]; + if (versionSpecificManifests.constructor === Array) { + // e.g. all['8.1.0'] === ['pkg.windows.appxmanifest', 'pkg.phone.appxmanifest'] + versionSpecificManifests.forEach(function(manifestFile) { + changes.push(createReplacement(manifestFile, change)); + }); + } + else { + // versionSpecificManifests is actually a single string + changes.push(createReplacement(versionSpecificManifests, change)); + } + }); + }); + } + + changes.forEach(function(change) { + change.xmls.forEach(function(xml) { + // 1. stringify each xml + var stringified = (new et.ElementTree(xml)).write({xml_declaration:false}); + // interp vars + if (vars) { + Object.keys(vars).forEach(function(key) { + var regExp = new RegExp('\\$' + key, 'g'); + stringified = stringified.replace(regExp, vars[key]); + }); + } + // 2. add into munge + mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after }); + }); + }); + return munge; +} + +// Go over the prepare queue and apply the config munges for each plugin +// that has been (un)installed. +PlatformMunger.prototype.process = PlatformMunger_process; +function PlatformMunger_process(plugins_dir) { + var self = this; + var platform_config = self.platformJson.root; + + // Uninstallation first + platform_config.prepare_queue.uninstalled.forEach(function(u) { + var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin)); + self.remove_plugin_changes(pluginInfo, u.topLevel); + }); + + // 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); + }); + + // Empty out installed/ uninstalled queues. + platform_config.prepare_queue.uninstalled = []; + platform_config.prepare_queue.installed = []; +} +/**** END of PlatformMunger ****/ http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js b/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js new file mode 100644 index 0000000..dd9ebbc --- /dev/null +++ b/node_modules/cordova-common/src/ConfigChanges/ConfigFile.js @@ -0,0 +1,208 @@ +/* + * 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. + * +*/ + +var fs = require('fs'); +var path = require('path'); + +var bplist = require('bplist-parser'); +var et = require('elementtree'); +var glob = require('glob'); +var plist = require('plist'); + +var plist_helpers = require('../util/plist-helpers'); +var xml_helpers = require('../util/xml-helpers'); + +/****************************************************************************** +* ConfigFile class +* +* Can load and keep various types of config files. Provides some functionality +* specific to some file types such as grafting XML children. In most cases it +* should be instantiated by ConfigKeeper. +* +* For plugin.xml files use as: +* plugin_config = self.config_keeper.get(plugin_dir, '', 'plugin.xml'); +* +* TODO: Consider moving it out to a separate file and maybe partially with +* overrides in platform handlers. +******************************************************************************/ +function ConfigFile(project_dir, platform, file_tag) { + this.project_dir = project_dir; + this.platform = platform; + this.file_tag = file_tag; + this.is_changed = false; + + this.load(); +} + +// ConfigFile.load() +ConfigFile.prototype.load = ConfigFile_load; +function ConfigFile_load() { + var self = this; + + // config file may be in a place not exactly specified in the target + var filepath = self.filepath = resolveConfigFilePath(self.project_dir, self.platform, self.file_tag); + + if ( !filepath || !fs.existsSync(filepath) ) { + self.exists = false; + return; + } + self.exists = true; + self.mtime = fs.statSync(self.filepath).mtime; + + var ext = path.extname(filepath); + // Windows8 uses an appxmanifest, and wp8 will likely use + // the same in a future release + if (ext == '.xml' || ext == '.appxmanifest') { + self.type = 'xml'; + self.data = xml_helpers.parseElementtreeSync(filepath); + } else { + // plist file + self.type = 'plist'; + // TODO: isBinaryPlist() reads the file and then parse re-reads it again. + // We always write out text plist, not binary. + // Do we still need to support binary plist? + // If yes, use plist.parseStringSync() and read the file once. + self.data = isBinaryPlist(filepath) ? + bplist.parseBuffer(fs.readFileSync(filepath)) : + plist.parse(fs.readFileSync(filepath, 'utf8')); + } +} + +ConfigFile.prototype.save = function ConfigFile_save() { + var self = this; + if (self.type === 'xml') { + fs.writeFileSync(self.filepath, self.data.write({indent: 4}), 'utf-8'); + } else { + // plist + var regExp = new RegExp('<string>[ \t\r\n]+?</string>', 'g'); + fs.writeFileSync(self.filepath, plist.build(self.data).replace(regExp, '<string></string>')); + } + self.is_changed = false; +}; + +ConfigFile.prototype.graft_child = function ConfigFile_graft_child(selector, xml_child) { + var self = this; + var filepath = self.filepath; + var result; + if (self.type === 'xml') { + 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.xml, selector); + if ( !result ) { + throw new Error('grafting to plist "' + filepath + '" during config install went bad :('); + } + } + self.is_changed = true; +}; + +ConfigFile.prototype.prune_child = function ConfigFile_prune_child(selector, xml_child) { + var self = this; + var filepath = self.filepath; + var result; + if (self.type === 'xml') { + 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.xml, selector); + } + if (!result) { + var err_msg = 'Pruning at selector "' + selector + '" from "' + filepath + '" went bad.'; + throw new Error(err_msg); + } + self.is_changed = true; +}; + +// Some config-file target attributes are not qualified with a full leading directory, or contain wildcards. +// Resolve to a real path in this function. +// TODO: getIOSProjectname is slow because of glob, try to avoid calling it several times per project. +function resolveConfigFilePath(project_dir, platform, file) { + var filepath = path.join(project_dir, file); + var matches; + + if (file.indexOf('*') > -1) { + // handle wildcards in targets using glob. + matches = glob.sync(path.join(project_dir, '**', file)); + if (matches.length) filepath = matches[0]; + + // [CB-5989] multiple Info.plist files may exist. default to $PROJECT_NAME-Info.plist + if(matches.length > 1 && file.indexOf('-Info.plist')>-1){ + var plistName = getIOSProjectname(project_dir)+'-Info.plist'; + for (var i=0; i < matches.length; i++) { + if(matches[i].indexOf(plistName) > -1){ + filepath = matches[i]; + break; + } + } + } + return filepath; + } + + // special-case config.xml target that is just "config.xml". This should be resolved to the real location of the file. + // TODO: move the logic that contains the locations of config.xml from cordova CLI into plugman. + if (file == 'config.xml') { + if (platform == 'ubuntu') { + filepath = path.join(project_dir, 'config.xml'); + } else if (platform == 'ios') { + var iospath = getIOSProjectname(project_dir); + filepath = path.join(project_dir,iospath, 'config.xml'); + } else if (platform == 'android') { + filepath = path.join(project_dir, 'res', 'xml', 'config.xml'); + } else { + matches = glob.sync(path.join(project_dir, '**', 'config.xml')); + if (matches.length) filepath = matches[0]; + } + return filepath; + } + + // None of the special cases matched, returning project_dir/file. + return filepath; +} + +// Find out the real name of an iOS project +// TODO: glob is slow, need a better way or caching, or avoid using more than once. +function getIOSProjectname(project_dir) { + var matches = glob.sync(path.join(project_dir, '*.xcodeproj')); + var iospath; + if (matches.length === 1) { + iospath = path.basename(matches[0],'.xcodeproj'); + } else { + var msg; + if (matches.length === 0) { + msg = 'Does not appear to be an xcode project, no xcode project file in ' + project_dir; + } else { + msg = 'There are multiple *.xcodeproj dirs in ' + project_dir; + } + throw new Error(msg); + } + return iospath; +} + +// determine if a plist file is binary +function isBinaryPlist(filename) { + // I wish there was a synchronous way to read only the first 6 bytes of a + // file. This is wasteful :/ + var buf = '' + fs.readFileSync(filename, 'utf8'); + // binary plists start with a magic header, "bplist" + return buf.substring(0, 6) === 'bplist'; +} + +module.exports = ConfigFile; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js b/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js new file mode 100644 index 0000000..894e922 --- /dev/null +++ b/node_modules/cordova-common/src/ConfigChanges/ConfigKeeper.js @@ -0,0 +1,65 @@ +/* + * 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. + * +*/ +/* jshint sub:true */ + +var path = require('path'); +var ConfigFile = require('./ConfigFile'); + +/****************************************************************************** +* ConfigKeeper class +* +* Used to load and store config files to avoid re-parsing and writing them out +* multiple times. +* +* The config files are referred to by a fake path constructed as +* project_dir/platform/file +* where file is the name used for the file in config munges. +******************************************************************************/ +function ConfigKeeper(project_dir, plugins_dir) { + this.project_dir = project_dir; + this.plugins_dir = plugins_dir; + this._cached = {}; +} + +ConfigKeeper.prototype.get = function ConfigKeeper_get(project_dir, platform, file) { + var self = this; + + // This fixes a bug with older plugins - when specifying config xml instead of res/xml/config.xml + // https://issues.apache.org/jira/browse/CB-6414 + if(file == 'config.xml' && platform == 'android'){ + file = 'res/xml/config.xml'; + } + var fake_path = path.join(project_dir, platform, file); + + if (self._cached[fake_path]) { + return self._cached[fake_path]; + } + // File was not cached, need to load. + var config_file = new ConfigFile(project_dir, platform, file); + self._cached[fake_path] = config_file; + return config_file; +}; + + +ConfigKeeper.prototype.save_all = function ConfigKeeper_save_all() { + var self = this; + Object.keys(self._cached).forEach(function (fake_path) { + var config_file = self._cached[fake_path]; + if (config_file.is_changed) config_file.save(); + }); +}; + +module.exports = ConfigKeeper; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ConfigChanges/munge-util.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ConfigChanges/munge-util.js b/node_modules/cordova-common/src/ConfigChanges/munge-util.js new file mode 100644 index 0000000..307b3c1 --- /dev/null +++ b/node_modules/cordova-common/src/ConfigChanges/munge-util.js @@ -0,0 +1,160 @@ +/* + * 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. + * +*/ +/* jshint sub:true */ + +var _ = require('underscore'); + +// add the count of [key1][key2]...[keyN] to obj +// return true if it didn't exist before +exports.deep_add = function deep_add(obj, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + + return exports.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 +exports.deep_remove = function deep_remove(obj, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + + var result = exports.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 +exports.deep_find = function deep_find(obj, keys /* or key1, key2 .... */ ) { + if ( !Array.isArray(keys) ) { + keys = Array.prototype.slice.call(arguments, 1); + } + + return exports.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 + +exports.process_munge = 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) { + return func(obj, k); + } else if (keys.length == 2) { + if (!obj.parents[k] && !createParents) { + return undefined; + } + obj.parents[k] = obj.parents[k] || []; + return exports.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 exports.process_munge(obj.files[k], createParents, func, keys.slice(1)); + } else { + throw new Error('Invalid key format. Must contain at most 3 elements (file, parent, xmlChild).'); + } +}; + +// All values from munge are added to base as +// base[file][selector][child] += munge[file][selector][child] +// Returns a munge object containing values that exist in munge +// but not in base. +exports.increment_munge = function increment_munge(base, munge) { + var diff = { files: {} }; + + 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 newlyAdded = exports.deep_add(base, [file, selector, val]); + if (newlyAdded) { + exports.deep_add(diff, file, selector, val); + } + } + } + } + return diff; +}; + +// Update the base munge object as +// base[file][selector][child] -= munge[file][selector][child] +// nodes that reached zero value are removed from base and added to the returned munge +// object. +exports.decrement_munge = function decrement_munge(base, munge) { + var zeroed = { files: {} }; + + 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 removed = exports.deep_remove(base, [file, selector, val]); + if (removed) { + exports.deep_add(zeroed, file, selector, val); + } + } + } + } + return zeroed; +}; + +// For better readability where used +exports.clone_munge = function clone_munge(munge) { + return exports.increment_munge({}, munge); +}; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ConfigParser/ConfigParser.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ConfigParser/ConfigParser.js b/node_modules/cordova-common/src/ConfigParser/ConfigParser.js new file mode 100644 index 0000000..7abddf6 --- /dev/null +++ b/node_modules/cordova-common/src/ConfigParser/ConfigParser.js @@ -0,0 +1,499 @@ +/** + 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. +*/ + +/* jshint sub:true */ + +var et = require('elementtree'), + xml= require('../util/xml-helpers'), + CordovaError = require('../CordovaError/CordovaError'), + fs = require('fs'), + events = require('../events'); + + +/** Wraps a config.xml file */ +function ConfigParser(path) { + this.path = path; + try { + this.doc = xml.parseElementtreeSync(path); + this.cdvNamespacePrefix = getCordovaNamespacePrefix(this.doc); + et.register_namespace(this.cdvNamespacePrefix, 'http://cordova.apache.org/ns/1.0'); + } catch (e) { + console.error('Parsing '+path+' failed'); + throw e; + } + var r = this.doc.getroot(); + if (r.tag !== 'widget') { + throw new CordovaError(path + ' has incorrect root node name (expected "widget", was "' + r.tag + '")'); + } +} + +function getNodeTextSafe(el) { + return el && el.text && el.text.trim(); +} + +function findOrCreate(doc, name) { + var ret = doc.find(name); + if (!ret) { + ret = new et.Element(name); + doc.getroot().append(ret); + } + return ret; +} + +function getCordovaNamespacePrefix(doc){ + var rootAtribs = Object.getOwnPropertyNames(doc.getroot().attrib); + var prefix = 'cdv'; + for (var j = 0; j < rootAtribs.length; j++ ) { + if(rootAtribs[j].indexOf('xmlns:') === 0 && + doc.getroot().attrib[rootAtribs[j]] === 'http://cordova.apache.org/ns/1.0'){ + var strings = rootAtribs[j].split(':'); + prefix = strings[1]; + break; + } + } + return prefix; +} + +/** + * Finds the value of an element's attribute + * @param {String} attributeName Name of the attribute to search for + * @param {Array} elems An array of ElementTree nodes + * @return {String} + */ +function findElementAttributeValue(attributeName, elems) { + + elems = Array.isArray(elems) ? elems : [ elems ]; + + var value = elems.filter(function (elem) { + return elem.attrib.name.toLowerCase() === attributeName.toLowerCase(); + }).map(function (filteredElems) { + return filteredElems.attrib.value; + }).pop(); + + return value ? value : ''; +} + +ConfigParser.prototype = { + packageName: function(id) { + return this.doc.getroot().attrib['id']; + }, + setPackageName: function(id) { + this.doc.getroot().attrib['id'] = id; + }, + android_packageName: function() { + return this.doc.getroot().attrib['android-packageName']; + }, + android_activityName: function() { + return this.doc.getroot().attrib['android-activityName']; + }, + ios_CFBundleIdentifier: function() { + return this.doc.getroot().attrib['ios-CFBundleIdentifier']; + }, + name: function() { + return getNodeTextSafe(this.doc.find('name')); + }, + setName: function(name) { + var el = findOrCreate(this.doc, 'name'); + el.text = name; + }, + description: function() { + return getNodeTextSafe(this.doc.find('description')); + }, + setDescription: function(text) { + var el = findOrCreate(this.doc, 'description'); + el.text = text; + }, + version: function() { + return this.doc.getroot().attrib['version']; + }, + windows_packageVersion: function() { + return this.doc.getroot().attrib('windows-packageVersion'); + }, + android_versionCode: function() { + return this.doc.getroot().attrib['android-versionCode']; + }, + ios_CFBundleVersion: function() { + return this.doc.getroot().attrib['ios-CFBundleVersion']; + }, + setVersion: function(value) { + this.doc.getroot().attrib['version'] = value; + }, + author: function() { + return getNodeTextSafe(this.doc.find('author')); + }, + getGlobalPreference: function (name) { + return findElementAttributeValue(name, this.doc.findall('preference')); + }, + setGlobalPreference: function (name, value) { + var pref = this.doc.find('preference[@name="' + name + '"]'); + if (!pref) { + pref = new et.Element('preference'); + pref.attrib.name = name; + this.doc.getroot().append(pref); + } + pref.attrib.value = value; + }, + getPlatformPreference: function (name, platform) { + return findElementAttributeValue(name, this.doc.findall('platform[@name=\'' + platform + '\']/preference')); + }, + getPreference: function(name, platform) { + + var platformPreference = ''; + + if (platform) { + platformPreference = this.getPlatformPreference(name, platform); + } + + return platformPreference ? platformPreference : this.getGlobalPreference(name); + + }, + /** + * Returns all resources for the platform specified. + * @param {String} platform The platform. + * @param {string} resourceName Type of static resources to return. + * "icon" and "splash" currently supported. + * @return {Array} Resources for the platform specified. + */ + getStaticResources: function(platform, resourceName) { + var ret = [], + staticResources = []; + if (platform) { // platform specific icons + this.doc.findall('platform[@name=\'' + platform + '\']/' + resourceName).forEach(function(elt){ + elt.platform = platform; // mark as platform specific resource + staticResources.push(elt); + }); + } + // root level resources + staticResources = staticResources.concat(this.doc.findall(resourceName)); + // parse resource elements + var that = this; + staticResources.forEach(function (elt) { + var res = {}; + res.src = elt.attrib.src; + res.density = elt.attrib['density'] || elt.attrib[that.cdvNamespacePrefix+':density'] || elt.attrib['gap:density']; + res.platform = elt.platform || null; // null means icon represents default icon (shared between platforms) + res.width = +elt.attrib.width || undefined; + res.height = +elt.attrib.height || undefined; + + // default icon + if (!res.width && !res.height && !res.density) { + ret.defaultResource = res; + } + ret.push(res); + }); + + /** + * Returns resource with specified width and/or height. + * @param {number} width Width of resource. + * @param {number} height Height of resource. + * @return {Resource} Resource object or null if not found. + */ + ret.getBySize = function(width, height) { + return ret.filter(function(res) { + if (!res.width && !res.height) { + return false; + } + return ((!res.width || (width == res.width)) && + (!res.height || (height == res.height))); + })[0] || null; + }; + + /** + * Returns resource with specified density. + * @param {string} density Density of resource. + * @return {Resource} Resource object or null if not found. + */ + ret.getByDensity = function(density) { + return ret.filter(function(res) { + return res.density == density; + })[0] || null; + }; + + /** Returns default icons */ + ret.getDefault = function() { + return ret.defaultResource; + }; + + return ret; + }, + + /** + * Returns all icons for specific platform. + * @param {string} platform Platform name + * @return {Resource[]} Array of icon objects. + */ + getIcons: function(platform) { + return this.getStaticResources(platform, 'icon'); + }, + + /** + * Returns all splash images for specific platform. + * @param {string} platform Platform name + * @return {Resource[]} Array of Splash objects. + */ + getSplashScreens: function(platform) { + return this.getStaticResources(platform, 'splash'); + }, + + /** + * Returns all hook scripts for the hook type specified. + * @param {String} hook The hook type. + * @param {Array} platforms Platforms to look for scripts into (root scripts will be included as well). + * @return {Array} Script elements. + */ + getHookScripts: function(hook, platforms) { + var self = this; + var scriptElements = self.doc.findall('./hook'); + + if(platforms) { + platforms.forEach(function (platform) { + scriptElements = scriptElements.concat(self.doc.findall('./platform[@name="' + platform + '"]/hook')); + }); + } + + function filterScriptByHookType(el) { + return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook; + } + + return scriptElements.filter(filterScriptByHookType); + }, + /** + * Returns a list of plugin (IDs). + * + * This function also returns any plugin's that + * were defined using the legacy <feature> tags. + * @return {string[]} Array of plugin IDs + */ + getPluginIdList: function () { + var plugins = this.doc.findall('plugin'); + var result = plugins.map(function(plugin){ + return plugin.attrib.name; + }); + var features = this.doc.findall('feature'); + features.forEach(function(element ){ + var idTag = element.find('./param[@name="id"]'); + if(idTag){ + result.push(idTag.attrib.value); + } + }); + return result; + }, + getPlugins: function () { + return this.getPluginIdList().map(function (pluginId) { + return this.getPlugin(pluginId); + }, this); + }, + /** + * Adds a plugin element. Does not check for duplicates. + * @name addPlugin + * @function + * @param {object} attributes name and spec are supported + * @param {Array|object} variables name, value or arbitary object + */ + addPlugin: function (attributes, variables) { + if (!attributes && !attributes.name) return; + var el = new et.Element('plugin'); + el.attrib.name = attributes.name; + if (attributes.spec) { + el.attrib.spec = attributes.spec; + } + + // support arbitrary object as variables source + if (variables && typeof variables === 'object' && !Array.isArray(variables)) { + variables = Object.keys(variables) + .map(function (variableName) { + return {name: variableName, value: variables[variableName]}; + }); + } + + if (variables) { + variables.forEach(function (variable) { + el.append(new et.Element('variable', { name: variable.name, value: variable.value })); + }); + } + this.doc.getroot().append(el); + }, + /** + * Retrives the plugin with the given id or null if not found. + * + * This function also returns any plugin's that + * were defined using the legacy <feature> tags. + * @name getPlugin + * @function + * @param {String} id + * @returns {object} plugin including any variables + */ + getPlugin: function(id){ + if(!id){ + return undefined; + } + var pluginElement = this.doc.find('./plugin/[@name="' + id + '"]'); + if (null === pluginElement) { + var legacyFeature = this.doc.find('./feature/param[@name="id"][@value="' + id + '"]/..'); + if(legacyFeature){ + events.emit('log', 'Found deprecated feature entry for ' + id +' in config.xml.'); + return featureToPlugin(legacyFeature); + } + return undefined; + } + var plugin = {}; + + plugin.name = pluginElement.attrib.name; + plugin.spec = pluginElement.attrib.spec || pluginElement.attrib.src || pluginElement.attrib.version; + plugin.variables = {}; + var variableElements = pluginElement.findall('variable'); + variableElements.forEach(function(varElement){ + var name = varElement.attrib.name; + var value = varElement.attrib.value; + if(name){ + plugin.variables[name] = value; + } + }); + return plugin; + }, + /** + * Remove the plugin entry with give name (id). + * + * This function also operates on any plugin's that + * were defined using the legacy <feature> tags. + * @name removePlugin + * @function + * @param id name of the plugin + */ + removePlugin: function(id){ + if(id){ + var plugins = this.doc.findall('./plugin/[@name="' + id + '"]') + .concat(this.doc.findall('./feature/param[@name="id"][@value="' + id + '"]/..')); + var children = this.doc.getroot().getchildren(); + plugins.forEach(function (plugin) { + var idx = children.indexOf(plugin); + if (idx > -1) { + children.splice(idx, 1); + } + }); + } + }, + + // Add any element to the root + addElement: function(name, attributes) { + var el = et.Element(name); + for (var a in attributes) { + el.attrib[a] = attributes[a]; + } + this.doc.getroot().append(el); + }, + + /** + * Adds an engine. Does not check for duplicates. + * @param {String} name the engine name + * @param {String} spec engine source location or version (optional) + */ + addEngine: function(name, spec){ + if(!name) return; + var el = et.Element('engine'); + el.attrib.name = name; + if(spec){ + el.attrib.spec = spec; + } + this.doc.getroot().append(el); + }, + /** + * Removes all the engines with given name + * @param {String} name the engine name. + */ + removeEngine: function(name){ + var engines = this.doc.findall('./engine/[@name="' +name+'"]'); + for(var i=0; i < engines.length; i++){ + var children = this.doc.getroot().getchildren(); + var idx = children.indexOf(engines[i]); + if(idx > -1){ + children.splice(idx,1); + } + } + }, + getEngines: function(){ + var engines = this.doc.findall('./engine'); + return engines.map(function(engine){ + var spec = engine.attrib.spec || engine.attrib.version; + return { + 'name': engine.attrib.name, + 'spec': spec ? spec : null + }; + }); + }, + /* Get all the access tags */ + getAccesses: function() { + var accesses = this.doc.findall('./access'); + return accesses.map(function(access){ + var minimum_tls_version = access.attrib['minimum-tls-version']; /* String */ + var requires_forward_secrecy = access.attrib['requires-forward-secrecy']; /* Boolean */ + return { + 'origin': access.attrib.origin, + 'minimum_tls_version': minimum_tls_version, + 'requires_forward_secrecy' : requires_forward_secrecy + }; + }); + }, + /* Get all the allow-navigation tags */ + getAllowNavigations: function() { + var allow_navigations = this.doc.findall('./allow-navigation'); + return allow_navigations.map(function(allow_navigation){ + var minimum_tls_version = allow_navigation.attrib['minimum-tls-version']; /* String */ + var requires_forward_secrecy = allow_navigation.attrib['requires-forward-secrecy']; /* Boolean */ + return { + 'href': allow_navigation.attrib.href, + 'minimum_tls_version': minimum_tls_version, + 'requires_forward_secrecy' : requires_forward_secrecy + }; + }); + }, + write:function() { + fs.writeFileSync(this.path, this.doc.write({indent: 4}), 'utf-8'); + } +}; + +function featureToPlugin(featureElement) { + var plugin = {}; + plugin.variables = []; + var pluginVersion, + pluginSrc; + + var nodes = featureElement.findall('param'); + nodes.forEach(function (element) { + var n = element.attrib.name; + var v = element.attrib.value; + if (n === 'id') { + plugin.name = v; + } else if (n === 'version') { + pluginVersion = v; + } else if (n === 'url' || n === 'installPath') { + pluginSrc = v; + } else { + plugin.variables[n] = v; + } + }); + + var spec = pluginSrc || pluginVersion; + if (spec) { + plugin.spec = spec; + } + + return plugin; +} +module.exports = ConfigParser; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/ConfigParser/README.md ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/ConfigParser/README.md b/node_modules/cordova-common/src/ConfigParser/README.md new file mode 100644 index 0000000..e5cd1bf --- /dev/null +++ b/node_modules/cordova-common/src/ConfigParser/README.md @@ -0,0 +1,86 @@ +<!-- +# +# 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. +# +--> + +# Cordova-Lib + +## ConfigParser + +wraps a valid cordova config.xml file + +### Usage + +### Include the ConfigParser module in a projet + + var ConfigParser = require('cordova-lib').configparser; + +### Create a new ConfigParser + + var config = new ConfigParser('path/to/config/xml/'); + +### Utility Functions + +#### packageName(id) +returns document root 'id' attribute value +#### Usage + + config.packageName: function(id) + +/* + * sets document root element 'id' attribute to @id + * + * @id - new id value + * + */ +#### setPackageName(id) +set document root 'id' attribute to + function(id) { + this.doc.getroot().attrib['id'] = id; + }, + +### + name: function() { + return getNodeTextSafe(this.doc.find('name')); + }, + setName: function(name) { + var el = findOrCreate(this.doc, 'name'); + el.text = name; + }, + +### read the description element + + config.description() + + var text = "New and improved description of App" + setDescription(text) + +### version management + version() + android_versionCode() + ios_CFBundleVersion() + setVersion() + +### read author element + + config.author(); + +### read preference + + config.getPreference(name); http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/CordovaError/CordovaError.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/CordovaError/CordovaError.js b/node_modules/cordova-common/src/CordovaError/CordovaError.js new file mode 100644 index 0000000..7262448 --- /dev/null +++ b/node_modules/cordova-common/src/CordovaError/CordovaError.js @@ -0,0 +1,91 @@ +/** + 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. +*/ + +/* jshint proto:true */ + +var EOL = require('os').EOL; + +/** + * A derived exception class. See usage example in cli.js + * Based on: + * stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript/8460753#8460753 + * @param {String} message Error message + * @param {Number} [code=0] Error code + * @param {CordovaExternalToolErrorContext} [context] External tool error context object + * @constructor + */ +function CordovaError(message, code, context) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = message; + this.code = code || CordovaError.UNKNOWN_ERROR; + this.context = context; +} +CordovaError.prototype.__proto__ = Error.prototype; + +// TODO: Extend error codes according the projects specifics +CordovaError.UNKNOWN_ERROR = 0; +CordovaError.EXTERNAL_TOOL_ERROR = 1; + +/** + * Translates instance's error code number into error code name, e.g. 0 -> UNKNOWN_ERROR + * @returns {string} Error code string name + */ +CordovaError.prototype.getErrorCodeName = function() { + for(var key in CordovaError) { + if(CordovaError.hasOwnProperty(key)) { + if(CordovaError[key] === this.code) { + return key; + } + } + } +}; + +/** + * Converts CordovaError instance to string representation + * @param {Boolean} [isVerbose] Set up verbose mode. Used to provide more + * details including information about error code name and context + * @return {String} Stringified error representation + */ +CordovaError.prototype.toString = function(isVerbose) { + var message = '', codePrefix = ''; + + if(this.code !== CordovaError.UNKNOWN_ERROR) { + codePrefix = 'code: ' + this.code + (isVerbose ? (' (' + this.getErrorCodeName() + ')') : '') + ' '; + } + + if(this.code === CordovaError.EXTERNAL_TOOL_ERROR) { + if(typeof this.context !== 'undefined') { + if(isVerbose) { + message = codePrefix + EOL + this.context.toString(isVerbose) + '\n failed with an error: ' + + this.message + EOL + 'Stack trace: ' + this.stack; + } else { + message = codePrefix + '\'' + this.context.toString(isVerbose) + '\' ' + this.message; + } + } else { + message = 'External tool failed with an error: ' + this.message; + } + } else { + message = isVerbose ? codePrefix + this.stack : codePrefix + this.message; + } + + return message; +}; + +module.exports = CordovaError; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js b/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js new file mode 100644 index 0000000..ca9a4aa --- /dev/null +++ b/node_modules/cordova-common/src/CordovaError/CordovaExternalToolErrorContext.js @@ -0,0 +1,48 @@ +/** + 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. + */ + +/* jshint proto:true */ + +var path = require('path'); + +/** + * @param {String} cmd Command full path + * @param {String[]} args Command args + * @param {String} [cwd] Command working directory + * @constructor + */ +function CordovaExternalToolErrorContext(cmd, args, cwd) { + this.cmd = cmd; + // Helper field for readability + this.cmdShortName = path.basename(cmd); + this.args = args; + this.cwd = cwd; +} + +CordovaExternalToolErrorContext.prototype.toString = function(isVerbose) { + if(isVerbose) { + return 'External tool \'' + this.cmdShortName + '\'' + + '\nCommand full path: ' + this.cmd + '\nCommand args: ' + this.args + + (typeof this.cwd !== 'undefined' ? '\nCommand cwd: ' + this.cwd : ''); + } + + return this.cmdShortName; +}; + +module.exports = CordovaExternalToolErrorContext; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/CordovaLogger.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/CordovaLogger.js b/node_modules/cordova-common/src/CordovaLogger.js new file mode 100644 index 0000000..06dbcf3 --- /dev/null +++ b/node_modules/cordova-common/src/CordovaLogger.js @@ -0,0 +1,203 @@ +/* + 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 ansi = require('ansi'); +var EventEmitter = require('events').EventEmitter; +var CordovaError = require('./CordovaError/CordovaError'); +var EOL = require('os').EOL; + +var INSTANCE; + +/** + * @class CordovaLogger + * + * Implements logging facility that anybody could use. Should not be + * instantiated directly, `CordovaLogger.get()` method should be used instead + * to acquire logger instance + */ +function CordovaLogger () { + this.levels = {}; + this.colors = {}; + this.stdout = process.stdout; + this.stderr = process.stderr; + + this.stdoutCursor = ansi(this.stdout); + this.stderrCursor = ansi(this.stderr); + + this.addLevel('verbose', 1000, 'grey'); + this.addLevel('normal' , 2000); + this.addLevel('warn' , 2000, 'yellow'); + this.addLevel('info' , 3000, 'blue'); + this.addLevel('error' , 5000, 'red'); + this.addLevel('results' , 10000); + + this.setLevel('normal'); +} + +/** + * Static method to create new or acquire existing instance. + * + * @return {CordovaLogger} Logger instance + */ +CordovaLogger.get = function () { + return INSTANCE || (INSTANCE = new CordovaLogger()); +}; + +CordovaLogger.VERBOSE = 'verbose'; +CordovaLogger.NORMAL = 'normal'; +CordovaLogger.WARN = 'warn'; +CordovaLogger.INFO = 'info'; +CordovaLogger.ERROR = 'error'; +CordovaLogger.RESULTS = 'results'; + +/** + * Emits log message to process' stdout/stderr depending on message's severity + * and current log level. If severity is less than current logger's level, + * then the message is ignored. + * + * @param {String} logLevel The message's log level. The logger should have + * corresponding level added (via logger.addLevel), otherwise + * `CordovaLogger.NORMAL` level will be used. + * @param {String} message The message, that should be logged to process' + * stdio + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.log = function (logLevel, message) { + // if there is no such logLevel defined, or provided level has + // less severity than active level, then just ignore this call and return + if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) + // return instance to allow to chain calls + return this; + + var isVerbose = this.logLevel === 'verbose'; + var cursor = this.stdoutCursor; + + if(message instanceof Error || logLevel === CordovaLogger.ERROR) { + message = formatError(message, isVerbose); + cursor = this.stderrCursor; + } + + var color = this.colors[logLevel]; + if (color) { + cursor.bold().fg[color](); + } + + cursor.write(message).reset().write(EOL); + + return this; +}; + +/** + * Adds a new level to logger instance. This method also creates a shortcut + * method to log events with the level provided (i.e. after adding new level + * 'debug', the method `debug(message)`, equal to logger.log('debug', message), + * will be added to logger instance) + * + * @param {String} level A log level name. The levels with the following + * names added by default to every instance: 'verbose', 'normal', 'warn', + * 'info', 'error', 'results' + * @param {Number} severity A number that represents level's severity. + * @param {String} color A valid color name, that will be used to log + * messages with this level. Any CSS color code or RGB value is allowed + * (according to ansi documentation: + * https://github.com/TooTallNate/ansi.js#features) + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.addLevel = function (level, severity, color) { + + this.levels[level] = severity; + + if (color) { + this.colors[level] = color; + } + + // Define own method with corresponding name + if (!this[level]) { + this[level] = this.log.bind(this, level); + } + + return this; +}; + +/** + * Sets the current logger level to provided value. If logger doesn't have level + * with this name, `CordovaLogger.NORMAL` will be used. + * + * @param {String} logLevel Level name. The level with this name should be + * added to logger before. + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.setLevel = function (logLevel) { + this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL; + + return this; +}; + +/** + * Attaches logger to EventEmitter instance provided. + * + * @param {EventEmitter} eventEmitter An EventEmitter instance to attach + * logger to. + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.subscribe = function (eventEmitter) { + + if (!(eventEmitter instanceof EventEmitter)) + throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); + + eventEmitter.on('verbose', this.verbose) + .on('log', this.normal) + .on('info', this.info) + .on('warn', this.warn) + .on('warning', this.warn) + // Set up event handlers for logging and results emitted as events. + .on('results', this.results); + + return this; +}; + +function formatError(error, isVerbose) { + var message = ''; + + if(error instanceof CordovaError) { + message = error.toString(isVerbose); + } else if(error instanceof Error) { + if(isVerbose) { + message = error.stack; + } else { + message = error.message; + } + } else { + // Plain text error message + message = error; + } + + if(message.toUpperCase().indexOf('ERROR:') !== 0) { + // Needed for backward compatibility with external tools + message = 'Error: ' + message; + } + + return message; +} + +module.exports = CordovaLogger; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/PlatformJson.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/PlatformJson.js b/node_modules/cordova-common/src/PlatformJson.js new file mode 100644 index 0000000..793e976 --- /dev/null +++ b/node_modules/cordova-common/src/PlatformJson.js @@ -0,0 +1,155 @@ +/* + * 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. + * +*/ +/* jshint sub:true */ + +var fs = require('fs'); +var path = require('path'); +var shelljs = require('shelljs'); +var mungeutil = require('./ConfigChanges/munge-util'); +var pluginMappernto = require('cordova-registry-mapper').newToOld; +var pluginMapperotn = require('cordova-registry-mapper').oldToNew; + +function PlatformJson(filePath, platform, root) { + this.filePath = filePath; + this.platform = platform; + this.root = fix_munge(root || {}); +} + +PlatformJson.load = function(plugins_dir, platform) { + var filePath = path.join(plugins_dir, platform + '.json'); + var root = null; + if (fs.existsSync(filePath)) { + root = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } + return new PlatformJson(filePath, platform, root); +}; + +PlatformJson.prototype.save = function() { + shelljs.mkdir('-p', path.dirname(this.filePath)); + fs.writeFileSync(this.filePath, JSON.stringify(this.root, null, 4), 'utf-8'); +}; + +/** + * Indicates whether the specified plugin is installed as a top-level (not as + * dependency to others) + * @method function + * @param {String} pluginId A plugin id to check for. + * @return {Boolean} true if plugin installed as top-level, otherwise false. + */ +PlatformJson.prototype.isPluginTopLevel = function(pluginId) { + var installedPlugins = this.root.installed_plugins; + return installedPlugins[pluginId] || + installedPlugins[pluginMappernto[pluginId]] || + installedPlugins[pluginMapperotn[pluginId]]; +}; + +/** + * Indicates whether the specified plugin is installed as a dependency to other + * plugin. + * @method function + * @param {String} pluginId A plugin id to check for. + * @return {Boolean} true if plugin installed as a dependency, otherwise false. + */ +PlatformJson.prototype.isPluginDependent = function(pluginId) { + var dependentPlugins = this.root.dependent_plugins; + return dependentPlugins[pluginId] || + dependentPlugins[pluginMappernto[pluginId]] || + dependentPlugins[pluginMapperotn[pluginId]]; +}; + +/** + * Indicates whether plugin is installed either as top-level or as dependency. + * @method function + * @param {String} pluginId A plugin id to check for. + * @return {Boolean} true if plugin installed, otherwise false. + */ +PlatformJson.prototype.isPluginInstalled = function(pluginId) { + return this.isPluginTopLevel(pluginId) || + this.isPluginDependent(pluginId); +}; + +PlatformJson.prototype.addPlugin = function(pluginId, variables, isTopLevel) { + var pluginsList = isTopLevel ? + this.root.installed_plugins : + this.root.dependent_plugins; + + pluginsList[pluginId] = variables; + + return this; +}; + +PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) { + var pluginsList = isTopLevel ? + this.root.installed_plugins : + this.root.dependent_plugins; + + delete pluginsList[pluginId]; + + 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.addUninstalledPluginToPrepareQueue = function(pluginId, is_top_level) { + this.root.prepare_queue.uninstalled.push({'plugin':pluginId, 'id':pluginId, 'topLevel':is_top_level}); +}; + +/** + * Moves plugin, specified by id to top-level plugins. If plugin is top-level + * already, then does nothing. + * @method function + * @param {String} pluginId A plugin id to make top-level. + * @return {PlatformJson} PlatformJson instance. + */ +PlatformJson.prototype.makeTopLevel = function(pluginId) { + var plugin = this.root.dependent_plugins[pluginId]; + if (plugin) { + delete this.root.dependent_plugins[pluginId]; + this.root.installed_plugins[pluginId] = plugin; + } + return this; +}; + +// convert a munge from the old format ([file][parent][xml] = count) to the current one +function fix_munge(root) { + root.prepare_queue = root.prepare_queue || {installed:[], uninstalled:[]}; + root.config_munge = root.config_munge || {files: {}}; + root.installed_plugins = root.installed_plugins || {}; + root.dependent_plugins = root.dependent_plugins || {}; + + var munge = root.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++) { + mungeutil.deep_add(new_munge, [file, selector, { xml: xml_child, count: val }]); + } + } + } + } + root.config_munge = new_munge; + } + + return root; +} + +module.exports = PlatformJson; + http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/a7f059c0/node_modules/cordova-common/src/PluginInfo/PluginInfo.js ---------------------------------------------------------------------- diff --git a/node_modules/cordova-common/src/PluginInfo/PluginInfo.js b/node_modules/cordova-common/src/PluginInfo/PluginInfo.js new file mode 100644 index 0000000..2554a3c --- /dev/null +++ b/node_modules/cordova-common/src/PluginInfo/PluginInfo.js @@ -0,0 +1,410 @@ +/** + 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. +*/ + +/* jshint sub:true, laxcomma:true, laxbreak:true */ + +/* +A class for holidng the information currently stored in plugin.xml +It should also be able to answer questions like whether the plugin +is compatible with a given engine version. + +TODO (kamrik): refactor this to not use sync functions and return promises. +*/ + + +var path = require('path') + , fs = require('fs') + , xml_helpers = require('../util/xml-helpers') + , CordovaError = require('../CordovaError/CordovaError') + ; + +function PluginInfo(dirname) { + var self = this; + + // METHODS + // Defined inside the constructor to avoid the "this" binding problems. + + // <preference> tag + // Example: <preference name="API_KEY" /> + // Used to require a variable to be specified via --variable when installing the plugin. + self.getPreferences = getPreferences; + function getPreferences(platform) { + var arprefs = _getTags(self._et, 'preference', platform, _parsePreference); + + var prefs= {}; + for(var i in arprefs) + { + var pref=arprefs[i]; + prefs[pref.preference]=pref.default; + } + // returns { key : default | null} + return prefs; + } + + function _parsePreference(prefTag) { + var name = prefTag.attrib.name.toUpperCase(); + var def = prefTag.attrib.default || null; + return {preference: name, default: def}; + } + + // <asset> + self.getAssets = getAssets; + function getAssets(platform) { + var assets = _getTags(self._et, 'asset', platform, _parseAsset); + return assets; + } + + function _parseAsset(tag) { + var src = tag.attrib.src; + var target = tag.attrib.target; + + if ( !src || !target) { + var msg = + 'Malformed <asset> tag. Both "src" and "target" attributes' + + 'must be specified in\n' + + self.filepath + ; + throw new Error(msg); + } + + var asset = { + itemType: 'asset', + src: src, + target: target + }; + return asset; + } + + + // <dependency> + // Example: + // <dependency id="com.plugin.id" + // url="https://github.com/myuser/someplugin" + // commit="428931ada3891801" + // subdir="some/path/here" /> + self.getDependencies = getDependencies; + function getDependencies(platform) { + var deps = _getTags( + self._et, + 'dependency', + platform, + _parseDependency + ); + return deps; + } + + function _parseDependency(tag) { + var dep = + { id : tag.attrib.id + , url : tag.attrib.url || '' + , subdir : tag.attrib.subdir || '' + , commit : tag.attrib.commit + }; + + dep.git_ref = dep.commit; + + if ( !dep.id ) { + var msg = + '<dependency> tag is missing id attribute in ' + + self.filepath + ; + throw new CordovaError(msg); + } + return dep; + } + + + // <config-file> tag + self.getConfigFiles = getConfigFiles; + function getConfigFiles(platform) { + var configFiles = _getTags(self._et, 'config-file', platform, _parseConfigFile); + return configFiles; + } + + function _parseConfigFile(tag) { + var configFile = + { target : tag.attrib['target'] + , parent : tag.attrib['parent'] + , after : tag.attrib['after'] + , xmls : tag.getchildren() + // To support demuxing via versions + , versions : tag.attrib['versions'] + , deviceTarget: tag.attrib['device-target'] + }; + return configFile; + } + + // <info> tags, both global and within a <platform> + // TODO (kamrik): Do we ever use <info> under <platform>? Example wanted. + self.getInfo = getInfo; + function getInfo(platform) { + var infos = _getTags( + self._et, + 'info', + platform, + function(elem) { return elem.text; } + ); + // Filter out any undefined or empty strings. + infos = infos.filter(Boolean); + return infos; + } + + // <source-file> + // Examples: + // <source-file src="src/ios/someLib.a" framework="true" /> + // <source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" /> + self.getSourceFiles = getSourceFiles; + function getSourceFiles(platform) { + var sourceFiles = _getTagsInPlatform(self._et, 'source-file', platform, _parseSourceFile); + return sourceFiles; + } + + function _parseSourceFile(tag) { + return { + itemType: 'source-file', + src: tag.attrib.src, + framework: isStrTrue(tag.attrib.framework), + weak: isStrTrue(tag.attrib.weak), + compilerFlags: tag.attrib['compiler-flags'], + targetDir: tag.attrib['target-dir'] + }; + } + + // <header-file> + // Example: + // <header-file src="CDVFoo.h" /> + self.getHeaderFiles = getHeaderFiles; + function getHeaderFiles(platform) { + var headerFiles = _getTagsInPlatform(self._et, 'header-file', platform, function(tag) { + return { + itemType: 'header-file', + src: tag.attrib.src, + targetDir: tag.attrib['target-dir'] + }; + }); + return headerFiles; + } + + // <resource-file> + // Example: + // <resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" device-target="win" arch="x86" versions=">=8.1" /> + self.getResourceFiles = getResourceFiles; + function getResourceFiles(platform) { + var resourceFiles = _getTagsInPlatform(self._et, 'resource-file', platform, function(tag) { + return { + itemType: 'resource-file', + src: tag.attrib.src, + target: tag.attrib.target, + versions: tag.attrib.versions, + deviceTarget: tag.attrib['device-target'], + arch: tag.attrib.arch + }; + }); + return resourceFiles; + } + + // <lib-file> + // Example: + // <lib-file src="src/BlackBerry10/native/device/libfoo.so" arch="device" /> + self.getLibFiles = getLibFiles; + function getLibFiles(platform) { + var libFiles = _getTagsInPlatform(self._et, 'lib-file', platform, function(tag) { + return { + itemType: 'lib-file', + src: tag.attrib.src, + arch: tag.attrib.arch, + Include: tag.attrib.Include, + versions: tag.attrib.versions, + deviceTarget: tag.attrib['device-target'] || tag.attrib.target + }; + }); + return libFiles; + } + + // <hook> + // Example: + // <hook type="before_build" src="scripts/beforeBuild.js" /> + self.getHookScripts = getHookScripts; + function getHookScripts(hook, platforms) { + var scriptElements = self._et.findall('./hook'); + + if(platforms) { + platforms.forEach(function (platform) { + scriptElements = scriptElements.concat(self._et.findall('./platform[@name="' + platform + '"]/hook')); + }); + } + + function filterScriptByHookType(el) { + return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook; + } + + return scriptElements.filter(filterScriptByHookType); + } + + self.getJsModules = getJsModules; + function getJsModules(platform) { + var modules = _getTags(self._et, 'js-module', platform, _parseJsModule); + return modules; + } + + function _parseJsModule(tag) { + var ret = { + itemType: 'js-module', + name: tag.attrib.name, + src: tag.attrib.src, + clobbers: tag.findall('clobbers').map(function(tag) { return { target: tag.attrib.target }; }), + merges: tag.findall('merges').map(function(tag) { return { target: tag.attrib.target }; }), + runs: tag.findall('runs').length > 0 + }; + + return ret; + } + + self.getEngines = function() { + return self._et.findall('engines/engine').map(function(n) { + return { + name: n.attrib.name, + version: n.attrib.version, + platform: n.attrib.platform, + scriptSrc: n.attrib.scriptSrc + }; + }); + }; + + self.getPlatforms = function() { + return self._et.findall('platform').map(function(n) { + return { name: n.attrib.name }; + }); + }; + + self.getPlatformsArray = function() { + return self._et.findall('platform').map(function(n) { + return n.attrib.name; + }); + }; + self.getFrameworks = function(platform) { + return _getTags(self._et, 'framework', platform, function(el) { + var ret = { + itemType: 'framework', + type: el.attrib.type, + parent: el.attrib.parent, + custom: isStrTrue(el.attrib.custom), + src: el.attrib.src, + weak: isStrTrue(el.attrib.weak), + versions: el.attrib.versions, + targetDir: el.attrib['target-dir'], + deviceTarget: el.attrib['device-target'] || el.attrib.target, + arch: el.attrib.arch + }; + return ret; + }); + }; + + self.getFilesAndFrameworks = getFilesAndFrameworks; + function getFilesAndFrameworks(platform) { + // Please avoid changing the order of the calls below, files will be + // installed in this order. + var items = [].concat( + self.getSourceFiles(platform), + self.getHeaderFiles(platform), + self.getResourceFiles(platform), + self.getFrameworks(platform), + self.getLibFiles(platform) + ); + return items; + } + ///// End of PluginInfo methods ///// + + + ///// PluginInfo Constructor logic ///// + self.filepath = path.join(dirname, 'plugin.xml'); + if (!fs.existsSync(self.filepath)) { + throw new CordovaError('Cannot find plugin.xml for plugin \'' + path.basename(dirname) + '\'. Please try adding it again.'); + } + + self.dir = dirname; + var et = self._et = xml_helpers.parseElementtreeSync(self.filepath); + var pelem = et.getroot(); + self.id = pelem.attrib.id; + self.version = pelem.attrib.version; + + // Optional fields + self.name = pelem.findtext('name'); + self.description = pelem.findtext('description'); + self.license = pelem.findtext('license'); + self.repo = pelem.findtext('repo'); + self.issue = pelem.findtext('issue'); + self.keywords = pelem.findtext('keywords'); + self.info = pelem.findtext('info'); + if (self.keywords) { + self.keywords = self.keywords.split(',').map( function(s) { return s.trim(); } ); + } + self.getKeywordsAndPlatforms = function () { + var ret = self.keywords || []; + return ret.concat('ecosystem:cordova').concat(addCordova(self.getPlatformsArray())); + }; +} // End of PluginInfo constructor. + +// Helper function used to prefix every element of an array with cordova- +// Useful when we want to modify platforms to be cordova-platform +function addCordova(someArray) { + var newArray = someArray.map(function(element) { + return 'cordova-' + element; + }); + return newArray; +} + +// Helper function used by most of the getSomething methods of PluginInfo. +// Get all elements of a given name. Both in root and in platform sections +// for the given platform. If transform is given and is a function, it is +// applied to each element. +function _getTags(pelem, tag, platform, transform) { + var platformTag = pelem.find('./platform[@name="' + platform + '"]'); + var tagsInRoot = pelem.findall(tag); + tagsInRoot = tagsInRoot || []; + var tagsInPlatform = platformTag ? platformTag.findall(tag) : []; + var tags = tagsInRoot.concat(tagsInPlatform); + if ( typeof transform === 'function' ) { + tags = tags.map(transform); + } + return tags; +} + +// Same as _getTags() but only looks inside a platfrom section. +function _getTagsInPlatform(pelem, tag, platform, transform) { + var platformTag = pelem.find('./platform[@name="' + platform + '"]'); + var tags = platformTag ? platformTag.findall(tag) : []; + if ( typeof transform === 'function' ) { + tags = tags.map(transform); + } + return tags; +} + +// Check if x is a string 'true'. +function isStrTrue(x) { + return String(x).toLowerCase() == 'true'; +} + +module.exports = PluginInfo; +// Backwards compat: +PluginInfo.PluginInfo = PluginInfo; +PluginInfo.loadPluginsDir = function(dir) { + var PluginInfoProvider = require('./PluginInfoProvider'); + return new PluginInfoProvider().getAllWithinSearchPath(dir); +}; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
